¿Deben los contenedores compararse como iguales usando el operador == cuando envuelven el mismo objeto?

19

Estoy escribiendo un contenedor para elementos XML que permite a un desarrollador analizar fácilmente los atributos del XML. El contenedor no tiene otro estado que el objeto que se está envolviendo.

Estoy considerando la siguiente implementación (simplificada para este ejemplo) que incluye una sobrecarga para el ==operador.

class XmlWrapper
{
    protected readonly XElement _element;

    public XmlWrapper(XElement element)
    {
        _element = element;
    }

    public string NameAttribute
    {
        get
        {
            //Get the value of the name attribute
        }
        set
        {
            //Set the value of the name attribute
        }
    }

    public override bool Equals(object other)
    {
        var o = other as XmlWrapper;
        if (o == null) return false;
        return _element.Equals(o._element);
    }

    public override int GetHashCode()
    {
        return _element.GetHashCode();
    }

    static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
    {
        if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) return true;
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false;

        return lhs._element == rhs._element;
    }

    static public bool operator != (XmlWrapper lhs, XmlWrapper rhs)
    {
        return !(lhs == rhs);
    }
}

Según entiendo idiomatic c #, el ==operador es para la igualdad de referencia, mientras que el Equals()método es para la igualdad de valores. Pero en este caso, el "valor" es solo una referencia al objeto que se está envolviendo. Por lo tanto, no tengo claro qué es convencional o idiomático para c #.

Por ejemplo, en este código ...

var underlyingElement = new XElement("Foo");
var a = new XmlWrapper(underlyingElement);
var b = new XmlWrapper(underlyingElement);

a.NameAttribute = "Hello";
b.NameAttribute = "World";

if (a == b)
{
    Console.WriteLine("The wrappers a and b are the same.");
}

.... ¿debería el programa generar "Los contenedores a y b son iguales"? ¿O sería extraño, es decir, violar el principio de menor asombro ?

John Wu
fuente
Por todas las veces que superé Equals, nunca superé ==(pero nunca al revés). ¿Es perezoso idiomático? Si obtengo un comportamiento diferente sin un reparto explícito que viola menos asombro.
radarbob
La respuesta a esto depende de lo que hace el NameAttribute: ¿modifica el elemento subyacente? ¿Es un dato adicional? El significado del código de ejemplo (y si debe considerarse igual) cambia dependiendo de eso, por lo que creo que debe completarlo.
Errores del
@Errorsatz Pido disculpas, pero quería mantener el ejemplo conciso, y asumí que estaba claro que modificaría el elemento envuelto (específicamente modificando el atributo XML llamado "nombre"). Pero los detalles apenas importan: el punto es que el contenedor permite el acceso de lectura / escritura al elemento envuelto pero no contiene ningún estado propio.
John Wu
44
Bueno, en este caso son importantes, significa que la asignación "Hola" y "Mundo" es engañosa, porque la última sobrescribirá a la primera. Lo que significa que estoy de acuerdo con la respuesta de Martin de que los dos pueden considerarse iguales. Si NameAttribute fuera realmente diferente entre ellos, no los consideraría iguales.
Errores el
2
"Según entiendo idiomático c #, el operador == es para la igualdad de referencia, mientras que el método Equals () es para la igualdad de valores". ¿Sin embargo, lo es? La mayoría de las veces que he visto == sobrecargado es para la igualdad de valores. El ejemplo más importante es System.String.
Arturo Torres Sánchez

Respuestas:

17

Dado que la referencia a la envoltura XElementes inmutable, no hay una diferencia externamente observable entre dos instancias de XmlWrapperesa envoltura del mismo elemento, por lo que tiene sentido sobrecargar ==para reflejar este hecho.

El código del cliente casi siempre se preocupa por la igualdad lógica (que, de forma predeterminada, se implementa utilizando la igualdad de referencia para los tipos de referencia). El hecho de que haya dos instancias en el montón es un detalle de implementación que a los clientes no debería importarles (y aquellos que sí usarán Object.ReferenceEqualsdirectamente).

casablanca
fuente
9

Si crees que tiene más sentido

La pregunta y la respuesta dependen de las expectativas del desarrollador , no es un requisito técnico.

SI considera que un contenedor no tiene una identidad y se define únicamente por su contenido, la respuesta a su pregunta es sí.

Pero este es un problema recurrente. ¿Deberían dos envoltorios exhibir igualdad cuando envuelven objetos diferentes pero con ambos objetos que tienen exactamente el mismo contenido?

La respuesta se repite. SI los objetos de contenido no tienen identidad personal y en su lugar están puramente definidos por su contenido, entonces los objetos de contenido son envoltorios que exhibirán igualdad. Si luego envuelve los objetos de contenido en otro contenedor, ese contenedor (adicional) también debería exhibir igualdad.

Son tortugas hasta el fondo .


Consejo general

Cada vez que se desvía del comportamiento predeterminado, debe documentarse explícitamente. Como desarrollador, espero que dos tipos de referencia no exhiban igualdad incluso si sus contenidos son iguales. Si cambia ese comportamiento, le sugiero que lo documente claramente para que todos los desarrolladores estén al tanto de este comportamiento atípico.


Según entiendo idiomatic c #, el ==operador es para la igualdad de referencia, mientras que el Equals()método es para la igualdad de valores.

Ese es su comportamiento predeterminado, pero esta no es una regla inamovible. Es una cuestión de convención, pero las convenciones se pueden cambiar donde sea justificable .

stringes un gran ejemplo aquí, ya ==que también es una verificación de igualdad de valores (¡incluso cuando no hay internados de cadenas!) ¿Por qué? En pocas palabras: porque tener cadenas se comportan como objetos de valor se siente más intuitivo para la mayoría de los desarrolladores.

Si su base de código (o la vida de sus desarrolladores) puede simplificarse notablemente haciendo que sus envoltorios exhiban igualdad de valor en todos los ámbitos, hágalo (pero documente ).

Si nunca necesita verificaciones de igualdad de referencia (o su dominio comercial las vuelve inútiles), entonces no tiene sentido mantener una verificación de igualdad de referencia. Es mejor reemplazarlo con una verificación de igualdad de valores para evitar errores de desarrollador .
Sin embargo, tenga en cuenta que si necesita verificaciones de igualdad de referencia más adelante, volver a implementarlo puede requerir un esfuerzo notable.

Flater
fuente
Tengo curiosidad por qué esperas que los tipos de referencia no definan la igualdad de contenido. La mayoría de los tipos no definen la igualdad simplemente porque no es esencial para su dominio, no porque quieran la igualdad de referencia.
Casablanca
3
@casablanca: Creo que está interpretando una definición diferente de "esperar" (es decir, requisito versus suposición). Sin documentación, espero (es decir, supongo) que ==verifica la igualdad de referencia, ya que este es el comportamiento predeterminado. Sin embargo, si ==realmente verifica la igualdad de valor, espero (es decir, requiero) que esto se documente explícitamente. I'm curious why you expect that reference types won't define content equality.No lo definen por defecto , pero eso no significa que no se pueda hacer. Nunca dije que no se puede (o no se debe) hacer, simplemente no espero (es decir, asumo) por defecto.
Flater
Ya veo lo que quieres decir, gracias por aclarar.
casablanca
2

Básicamente, está comparando cadenas, por lo que me sorprendería que dos contenedores que contengan el mismo contenido XML no se consideren iguales, ya sea que se verifique con Equals o ==.

La regla idiomática puede tener sentido para los objetos de tipo de referencia en general, pero las cadenas son especiales en un sentido idiomático, se supone que debe tratarlas y considerarlas como valores, aunque técnicamente son tipos de referencia.

Sin embargo, su postfix Wrapper agrega confusión. Básicamente dice "no es un elemento XML". Entonces, ¿debería tratarlo como un tipo de referencia después de todo? Semánticamente esto no tendría sentido. Estaría menos confundido si la clase se llamara XmlContent. Esto indicaría que nos importa el contenido, no los detalles técnicos de implementación.

Martin Maat
fuente
¿Dónde pondrías el límite entre "un objeto construido a partir de una cadena" y "básicamente una cadena"? Parece una definición bastante ambigua desde la perspectiva API externa. ¿El usuario de la API realmente tiene que adivinar las partes internas para adivinar el comportamiento?
Arthur Havlicek
@Arthur Semantics de la clase / objeto debería proporcionar la pista. Y a veces no es tan obvio, de ahí esta pregunta. Para los tipos de valor real será obvio para cualquiera. Para cadenas reales también. El tipo debería decir en última instancia si una comparación debe involucrar contenido o identidad de objeto.
Martin Maat