I wrapper devono comparare come uguali usando l'operatore == quando avvolgono lo stesso oggetto?


19

Sto scrivendo un wrapper per elementi XML che consente a uno sviluppatore di analizzare facilmente gli attributi dall'XML. Il wrapper non ha altro stato se non l'oggetto da avvolgere.

Sto prendendo in considerazione la seguente implementazione (semplificata per questo esempio) che include un sovraccarico per l' ==operatore.

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);
    }
}

Come ho capito idiomatico c #, l' ==operatore è per l'uguaglianza di riferimento mentre il Equals()metodo è per l'uguaglianza di valore. Ma in questo caso, il "valore" è solo un riferimento all'oggetto che viene spostato. Quindi non sono chiaro cosa sia convenzionale o idiomatico per c #.

Ad esempio, in questo codice ...

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.");
}

.... dovrebbe apparire il programma "I wrapper aeb sono uguali"? O sarebbe strano, vale a dire violare il principio del minimo stupore ?


Per tutte le volte che ho scavalcato Equalsnon ho mai scavalcato ==(ma mai il contrario). Pigro è idiomatico? Se ottengo un comportamento diverso senza un cast esplicito che viola il minimo stupore.
radarbob,

La risposta a ciò dipende da cosa fa NameAttribute: modifica l'elemento sottostante? È un dato aggiuntivo? Il significato del codice di esempio (e se dovrebbe essere considerato uguale) cambia in base a quello, quindi penso che sia necessario compilarlo.
Errori

@Errorsatz Chiedo scusa, ma volevo mantenere l'esempio conciso, e ho pensato che fosse chiaro che avrebbe modificato l'elemento racchiuso (in particolare modificando l'attributo XML chiamato "nome"). Ma i dettagli contano a malapena: il punto è che il wrapper consente l'accesso in lettura / scrittura all'elemento wrapped ma non contiene alcuno stato.
John Wu,

4
Bene, in questo caso sono importanti - significa che i compiti "Hello" e "World" sono fuorvianti, perché il secondo sovrascriverà il primo. Il che significa che sono d'accordo con la risposta di Martin che i due possono essere considerati uguali. Se il NameAttribute fosse effettivamente diverso tra loro, non li considererei uguali.
Errori

2
"A quanto ho capito idiomatico c #, l'operatore == è per l'uguaglianza di riferimento mentre il metodo Equals () è per l'uguaglianza di valore." È vero? La maggior parte delle volte che ho visto == sovraccarico è per l'uguaglianza di valore. L'esempio più importante è System.String.
Arturo Torres Sánchez,

Risposte:


17

Poiché il riferimento all'involucro XElementè immutabile, non vi è alcuna differenza osservabile esternamente tra due istanze di XmlWrapperquell'involucro dello stesso elemento, quindi ha senso sovraccaricare ==per riflettere questo fatto.

Il codice client si preoccupa quasi sempre dell'uguaglianza logica (che, per impostazione predefinita, viene implementata usando l'uguaglianza di riferimento per i tipi di riferimento). Il fatto che ci siano due istanze nell'heap è un dettaglio di implementazione di cui i clienti non dovrebbero preoccuparsi (e quelli che lo useranno Object.ReferenceEqualsdirettamente).


9

Se pensi che abbia più senso

La domanda e la risposta sono una questione di aspettative degli sviluppatori , questo non è un requisito tecnico.

Se ritieni che un wrapper non abbia un'identità e che sia definita esclusivamente dal suo contenuto, la risposta alla tua domanda è sì.

Ma questo è un problema ricorrente. Due wrapper dovrebbero mostrare uguaglianza quando avvolgono oggetti diversi ma con entrambi gli oggetti che hanno lo stesso identico contenuto?

La risposta si ripete. SE gli oggetti contenuto non hanno identità personale e invece sono puramente definiti dal loro contenuto, gli oggetti contenuto sono effettivamente wrapper che mostreranno uguaglianza. Se poi avvolgi gli oggetti contenuto in un altro wrapper, anche quel wrapper (aggiuntivo) dovrebbe mostrare uguaglianza.

Sono le tartarughe fino in fondo .


Consiglio generale

Ogni volta che si discosta dal comportamento predefinito, dovrebbe essere esplicitamente documentato. Come sviluppatore, mi aspetto che due tipi di riferimento non mostrino uguaglianza anche se i loro contenuti sono uguali. Se modifichi quel comportamento, ti suggerirei di documentarlo chiaramente in modo che tutti gli sviluppatori siano consapevoli di questo comportamento atipico.


Come ho capito idiomatico c #, l' ==operatore è per l'uguaglianza di riferimento mentre il Equals()metodo è per l'uguaglianza di valore.

Questo è il comportamento predefinito, ma questa non è una regola immobile. È una questione di convenzione, ma le convenzioni possono essere modificate ove giustificabile .

stringè un ottimo esempio qui, così come lo ==è anche un controllo di uguaglianza di valore (anche quando non c'è interning di stringhe!). Perché? In parole povere: perché avere stringhe si comporta come oggetti di valore sembra più intuitivo per la maggior parte degli sviluppatori.

Se la tua base di codice (o la vita dei tuoi sviluppatori) può essere notevolmente semplificata facendo in modo che i tuoi wrapper mostrino uguaglianza di valore su tutta la linea, provaci (ma documentalo ).

Se non hai mai bisogno di controlli di uguaglianza di riferimento (o sono resi inutili dal tuo dominio aziendale), non ha senso mantenere un controllo di uguaglianza di riferimento. È meglio quindi sostituirlo con un controllo di uguaglianza di valore in modo da prevenire errori dello sviluppatore .
Tuttavia, ti rendi conto che se avessi bisogno di controlli di uguaglianza di riferimento in un secondo momento, la reimplementazione potrebbe richiedere uno sforzo notevole.


Sono curioso di sapere perché ti aspetti che i tipi di riferimento non definiscano l'uguaglianza dei contenuti. La maggior parte dei tipi non definisce l'uguaglianza semplicemente perché non è essenziale per il proprio dominio, non perché vogliono l' uguaglianza di riferimento.
Casablanca,

3
@casablanca: Penso che tu stia interpretando una diversa definizione di "aspettati" (es. requisito contro presupposto). Senza documentazione, mi aspetto (cioè presumo) che ==controlli l'uguaglianza di riferimento in quanto questo è il comportamento predefinito. Tuttavia, se ==effettivamente controlla l'uguaglianza di valore, mi aspetto (cioè richiedo) che questo sia documentato esplicitamente. I'm curious why you expect that reference types won't define content equality.Non lo definiscono per impostazione predefinita , ma ciò non significa che non possa essere fatto. Non ho mai detto che non può (o non dovrebbe) essere fatto, semplicemente non mi aspetto (cioè presumo) di default.
Flater,

Capisco cosa intendi, grazie per il chiarimento.
Casablanca,

2

Stai fondamentalmente confrontando le stringhe, quindi sarei stupito se due wrapper con lo stesso contenuto XML non fossero considerati uguali, sia esso verificato usando Equals o ==.

La regola idiomatica può avere senso per gli oggetti del tipo di riferimento in generale, ma le stringhe sono speciali in un senso idiomatico, dovresti trattarle e considerarle come valori sebbene tecnicamente siano tipi di riferimento.

Il postfix del tuo wrapper aggiunge però confusione. In pratica dice "non un elemento XML". Quindi dovrei trattarlo come un tipo di riferimento dopo tutto? Semanticamente questo non avrebbe senso. Sarei meno confuso se la classe fosse chiamata XmlContent. Questo significherebbe che ci preoccupiamo dei contenuti, non dei dettagli tecnici di implementazione.


Dove metteresti il ​​limite tra "un oggetto costruito da una stringa" e "sostanzialmente una stringa"? Sembra una definizione abbastanza ambigua dal punto di vista dell'API esterna. L'utente dell'API deve effettivamente indovinare gli interni per indovinare il comportamento?
Arthur Havlicek,

@Arthur La semantica della classe / oggetto dovrebbe fornire la chiave. E a volte non è poi così ovvio, quindi questa domanda. Per i tipi di valore reale sarà ovvio per chiunque. Anche per stringhe reali. Il tipo dovrebbe in definitiva dire se un confronto dovrebbe includere il contenuto o l'identità dell'oggetto.
Martin Maat,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.