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

19

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 Equals()método é 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 ?

John Wu
fonte
Por todas as vezes que eu substituí, Equalseu nunca substituí ==(mas nunca o contrário). O idiota preguiçoso? Se eu tiver um comportamento diferente sem um elenco explícito que viole menos espanto.
radarbob 10/09
A resposta para isso depende do que o NameAttribute faz - modifica o elemento subjacente? É um dado adicional? O significado do código de exemplo (e se deve ser considerado igual) muda dependendo disso, então acho que você precisa preenchê-lo.
Errosatz
@Errorsatz Peço desculpas, mas queria manter o exemplo conciso e presumi que estava claro que ele modificaria o elemento encapsulado (especificamente modificando o atributo XML chamado "nome".) Mas as especificidades quase não importam - o ponto é que o wrapper permite acesso de leitura / gravação ao elemento empacotado, mas não contém nenhum estado próprio.
John Wu
4
Bem, nesse caso, eles são importantes - significa que a atribuição "Olá" e "Mundo" é enganosa, porque a última substituirá a primeira. O que significa que eu concordo com a resposta de Martin de que os dois podem ser considerados iguais. Se o NameAttribute fosse realmente diferente entre eles, eu não os consideraria iguais.
Errosatz
2
"Como eu entendo c # idiomático, o operador == é para igualdade de referência, enquanto o método Equals () é para igualdade de valor." É mesmo? Na maioria das vezes que eu vi sobrecarregado, é para igualdade de valor. O exemplo mais importante é System.String.
Arturo Torres Sánchez

Respostas:

17

Como a referência ao empacotado XElementé imutável, não há diferença observável externamente entre duas instâncias XmlWrapperdesse empacotamento no 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 preocupar (e aqueles que o fizerem usarão Object.ReferenceEqualsdiretamente).

Casablanca
fonte
9

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 a define apenas pelo conteúdo, a resposta para sua pergunta é sim.

Mas este é um problema recorrente. Dois invólucros 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 tiverem identidade pessoal e, em vez disso, forem puramente definidos por seu conteúdo, eles serão efetivamente invólucros que exibirão igualdade. Se você agrupar os objetos de conteúdo em outro invólucro, esse invólucro (adicional) também deverá exibir igualdade.

São 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 Equals()método é 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, como ==também é uma verificação de igualdade de valor (mesmo quando não há cadeia de caracteres interna!). Por quê? Simplificando: porque ter seqüências de caracteres se comporta 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 isso (mas documente ).

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 faz sentido manter uma verificação de igualdade de referência. É melhor substituí-lo por uma verificação de igualdade de valor para evitar erros 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.

Flater
fonte
Estou curioso para saber por que você espera que os tipos de referência não definam a igualdade de conteúdo. A maioria dos tipos não define igualdade simplesmente porque não é essencial para seu domínio, não porque eles querem igualdade de referência.
casablanca
3
@casablanca: Eu acho que você está interpretando uma definição diferente de "expectativa" (ou seja, requisito versus suposição). Sem documentação, espero (ou seja, assuma) que ==verifique a igualdade de referência, pois esse é o comportamento padrão. No entanto, se ==realmente verificar a igualdade de valor, espero (ou seja, exigir) que isso seja documentado explicitamente. I'm curious why you expect that reference types won't define content equality.Eles não o definem por padrão , mas isso não significa que não possa ser feito. Eu nunca disse que não pode (ou não deveria) ser feito, simplesmente não espero (ou seja, assumo) isso por padrão.
Flater
Entendo o que você quer dizer, obrigado por esclarecer.
casablanca
2

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 fosse nomeada XmlContent. Isso indicaria que nos preocupamos com o conteúdo, não com os detalhes técnicos da implementação.

Martin Maat
fonte
Onde você colocaria o limite entre "um objeto criado a partir de uma string" e "basicamente uma string"? Parece uma definição bastante ambígua da perspectiva externa da API. O usuário da API realmente precisa adivinhar os internos para adivinhar o comportamento?
Arthur Havlicek 10/09
@Arthur Semantics da classe / objeto deve fornecer a pista. E às vezes não é tão óbvio, daí a questão. Para tipos de valores reais, será óbvio para qualquer pessoa. Para strings reais também. O tipo deve finalmente dizer se uma comparação deve envolver conteúdo ou identidade de objeto.
Martin Maat