ti-enxame.com

Os wrappers devem ser comparados com o operador == quando eles envolvem o mesmo objeto?

Estou escrevendo um wrapper para elementos XML que permite que um desenvolvedor analise facilmente atributos do XML. O wrapper não possui outro estado além do objeto que está sendo quebrado.

Estou considerando a seguinte implementação (simplificada para este exemplo), que inclui uma sobrecarga para o 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);
    }
}

Pelo que entendi c # idiomático, o operador == É para igualdade de referência, enquanto o método Equals() é para igualdade de valor. Mas, neste caso, o "valor" é apenas uma referência ao objeto que está sendo quebrado. Portanto, não estou claro o que é convencional ou idiomático para c #.

Por exemplo, neste 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.");
}

.... o programa deve exibir "Os wrappers aeb são iguais"? Ou isso seria estranho, ou seja, violaria o princípio de menor espanto ?

18
John Wu

Como a referência ao XElement encapsulado é imutável, não há diferença observável externamente entre duas instâncias de XmlWrapper que envolvem o mesmo elemento; portanto, faz sentido sobrecarregar == para refletir esse fato.

O código do cliente quase sempre se preocupa com a igualdade lógica (que, por padrão, é implementada usando a igualdade de referência para tipos de referência). O fato de haver duas instâncias no heap é um detalhe de implementação com o qual os clientes não devem se importar (e aqueles que o fizerem usarão Object.ReferenceEquals diretamente).

16
casablanca

Se você acha que faz mais sentido

A pergunta e a resposta são uma questão de expectativa do desenvolvedor , esse não é um requisito técnico.

[~ # ~] se [~ # ~] você considera que um invólucro não possui uma identidade e é definido apenas pelo conteúdo, então a resposta para sua pergunta é sim.

Mas este é um problema recorrente. Dois wrappers devem exibir igualdade quando envolvem objetos diferentes, mas com os dois objetos com exatamente o mesmo conteúdo?

A resposta se repete. [~ # ~] se [~ # ~] os objetos de conteúdo não têm identidade pessoal e, em vez disso, são puramente definidos por seu conteúdo, os objetos de conteúdo são efetivamente invólucros que exibem igualdade. Se você agrupar os objetos de conteúdo em outro invólucro, esse invólucro (adicional) também deverá exibir igualdade.

É tartarugas até o fim .


Dica geral

Sempre que você se desvia do comportamento padrão, ele deve ser explicitamente documentado. Como desenvolvedor, espero que dois tipos de referência não apresentem igualdade, mesmo que seu conteúdo seja igual. Se você mudar esse comportamento, sugiro que você o documente claramente para que todos os desenvolvedores estejam cientes desse comportamento atípico.


Pelo que entendi c # idiomático, o operador == É para igualdade de referência, enquanto o método Equals() é para igualdade de valor.

Esse é o seu comportamento padrão, mas essa não é uma regra imóvel. É uma questão de convenção, mas as convenções podem ser alteradas quando justificadas .

string é um ótimo exemplo aqui, pois == também é uma verificação de igualdade de valor (mesmo quando não há cadeia de caracteres interna)! Por quê? Simplificando: porque ter cordas se comportam como objetos de valor parece mais intuitivo para a maioria dos desenvolvedores.

Se a sua base de código (ou a vida de seus desenvolvedores) puder ser notavelmente simplificada, fazendo com que seus wrappers exibam igualdade de valor em todos os aspectos, faça-o (mas documente-o ).

Se você nunca exige verificações de igualdade de referência (ou elas são inúteis pelo domínio de negócios), não há sentido em manter uma verificação de igualdade de referência. É melhor substituí-lo por uma verificação de igualdade de valor para evitar erro do desenvolvedor .
No entanto, saiba que, se precisar de verificações de igualdade de referência posteriormente, reimplementá-lo pode exigir um esforço notável.

9
Flater

Você está basicamente comparando cadeias de caracteres, então eu ficaria surpreso se dois wrappers contendo o mesmo conteúdo XML não fossem considerados iguais, seja verificado usando Equals ou ==.

A regra idiomática pode fazer sentido para objetos do tipo de referência em geral, mas as strings são especiais em um sentido idiomático; você deve tratá-las e considerá-las como valores, embora tecnicamente sejam tipos de referência.

O seu postfix do Wrapper acrescenta confusão. Basicamente, diz "não é um elemento XML". Então, devo tratá-lo como um tipo de referência, afinal? Semanticamente, isso não faria sentido. Eu ficaria menos confuso se a classe tivesse o nome XmlContent. Isso indica que nos preocupamos com o conteúdo, não com os detalhes técnicos da implementação.

2
Martin Maat