Sollten Wrapper mit dem Operator == als gleich verglichen werden, wenn sie dasselbe Objekt umschließen?

19

Ich schreibe einen Wrapper für XML-Elemente, mit dem ein Entwickler Attribute einfach aus dem XML-Code analysieren kann. Der Wrapper hat keinen anderen Status als das Objekt, das umbrochen wird.

Ich denke an die folgende Implementierung (vereinfacht für dieses Beispiel), die eine Überlastung für den ==Bediener beinhaltet.

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

Nach meinem Verständnis für C # dient der ==Operator der Referenzgleichheit, während die Equals()Methode der Wertgleichheit dient. In diesem Fall ist der "Wert" jedoch nur ein Verweis auf das Objekt, das umgebrochen wird. Mir ist also nicht klar, was für c # konventionell oder idiomatisch ist.

Zum Beispiel in diesem Code ...

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

.... sollte das Programm "Die Wrapper a und b sind gleich" ausgeben? Oder wäre das seltsam, das Prinzip des geringsten Erstaunens zu verletzen ?

John Wu
quelle
Während all der Zeiten, die ich überschritt, überschritt Equalsich nie ==(aber niemals umgekehrt). Ist faul idiomatisch? Wenn ich ein anderes Verhalten ohne eine explizite Besetzung bekomme, die am wenigsten gegen das Erstaunen verstößt.
Radarbob
Die Antwort darauf hängt davon ab, was das NameAttribute tut - ändert es das zugrunde liegende Element? Handelt es sich um zusätzliche Daten? Die Bedeutung des Beispielcodes (und ob er als gleich angesehen werden sollte) ändert sich in Abhängigkeit davon. Ich denke, Sie müssen ihn ausfüllen.
Errorsatz
@Errorsatz Ich entschuldige mich, aber ich wollte das Beispiel kurz halten, und ich nahm an, dass klar war, dass es das umgebrochene Element ändern würde (insbesondere durch Ändern des XML-Attributs "name".) Aber die Einzelheiten spielen kaum eine Rolle - der Punkt ist dass der Wrapper Lese- / Schreibzugriff auf das umschlossene Element erlaubt, aber keinen eigenen Zustand enthält.
John Wu
4
Nun, in diesem Fall sind sie wichtig - es bedeutet, dass die Zuweisung von "Hallo" und "Welt" irreführend ist, weil die letztere die erstere überschreibt. Das heißt, ich stimme Martins Antwort zu, dass die beiden als gleich angesehen werden können. Wenn sich die Namensattribute tatsächlich voneinander unterscheiden würden, würde ich sie nicht als gleich betrachten.
Errorsatz
2
"Nach meinem Verständnis von idiomatischem c # dient der Operator == der Referenzgleichheit, während die Methode Equals () der Wertgleichheit dient." Ist es trotzdem? Meistens habe ich gesehen, dass == überladen für Wertgleichheit ist. Das wichtigste Beispiel ist System.String.
Arturo Torres Sánchez

Antworten:

17

Da der Verweis auf die Umhüllung XElementunveränderlich ist, gibt es keinen äußerlich erkennbaren Unterschied zwischen zwei Instanzen XmlWrapperdieser Umhüllung desselben Elements. Daher ist es sinnvoll, eine Überladung vorzunehmen ==, um diese Tatsache widerzuspiegeln.

Client-Code kümmert sich fast immer um die logische Gleichheit (die standardmäßig unter Verwendung der Referenzgleichheit für Referenztypen implementiert wird). Die Tatsache, dass sich zwei Instanzen auf dem Heap befinden, ist ein Implementierungsdetail, das Clients nicht interessieren sollte (und die, die dies tun, werden es Object.ReferenceEqualsdirekt verwenden).

casablanca
quelle
9

Wenn Sie denken, dass es am sinnvollsten ist

Die Frage und Antwort ist eine Frage der Entwicklererwartung , dies ist keine technische Anforderung.

Wenn Sie der Meinung sind, dass ein Wrapper keine Identität hat und nur durch seinen Inhalt definiert ist, lautet die Antwort auf Ihre Frage Ja.

Dies ist jedoch ein immer wiederkehrendes Problem. Sollten zwei Wrapper gleich sein, wenn sie unterschiedliche Objekte umschließen, aber beide Objekte den exakt gleichen Inhalt haben?

Die Antwort wiederholt sich. Wenn die Inhaltsobjekte keine persönliche Identität haben und stattdessen nur durch ihren Inhalt definiert sind, sind die Inhaltsobjekte effektiv Wrapper, die Gleichheit aufweisen. Wenn Sie dann die Inhaltsobjekte in einen anderen Wrapper einwickeln, sollte dieser (zusätzliche) Wrapper ebenfalls Gleichheit aufweisen.

Es ist Schildkröten den ganzen Weg nach unten .


Allgemeiner Tipp

Wann immer Sie vom Standardverhalten abweichen, sollte dies explizit dokumentiert werden. Als Entwickler erwarte ich, dass zwei Referenztypen nicht gleich sind, auch wenn ihr Inhalt gleich ist. Wenn Sie dieses Verhalten ändern, sollten Sie es klar dokumentieren, damit alle Entwickler über dieses atypische Verhalten informiert sind.


Nach meinem Verständnis für C # dient der ==Operator der Referenzgleichheit, während die Equals()Methode der Wertgleichheit dient.

Das ist das Standardverhalten, aber dies ist keine unbewegliche Regel. Es ist eine Frage der Konvention, aber Konventionen können geändert werden, wo dies gerechtfertigt ist .

stringist hier ein großartiges Beispiel, ebenso wie ==eine Wertegleichheitsprüfung (auch wenn kein String interniert ist!). Warum? Einfach ausgedrückt: Da sich Strings wie Wertobjekte verhalten, fühlen sich die meisten Entwickler intuitiver an.

Wenn sich Ihre Codebasis (oder das Leben Ihrer Entwickler) erheblich vereinfachen lässt, indem Ihre Wrapper auf ganzer Linie die gleiche Wertschätzung aufweisen, sollten Sie sie wählen (aber dokumentieren ).

Wenn Sie niemals Referenzgleichheitsprüfungen benötigen (oder diese von Ihrer Unternehmensdomäne unbrauchbar gemacht werden), ist es nicht sinnvoll, eine Referenzgleichheitsprüfung durchzuführen. Es ist besser, es durch eine Wertegleichheitsprüfung zu ersetzen, um Entwicklerfehler zu vermeiden .
Beachten Sie jedoch, dass die Neuimplementierung von Referenzgleichheitsprüfungen zu einem späteren Zeitpunkt einige Zeit in Anspruch nehmen kann.

Flater
quelle
Ich bin gespannt, warum Sie davon ausgehen, dass Referenztypen die Inhaltsgleichheit nicht definieren. Die meisten Typen definieren Gleichheit nicht einfach, weil sie für ihre Domäne nicht wesentlich sind, nicht weil sie eine Referenzgleichheit wünschen .
Casablanca
3
@casablanca: Ich denke, Sie interpretieren eine andere Definition von "erwarten" (dh Anforderung versus Annahme). Ohne Dokumentation erwarte ich (dh nehme an), dass ==auf Referenzgleichheit geprüft wird, da dies das Standardverhalten ist. Wenn jedoch ==tatsächlich auf Wertgleichheit geprüft wird, erwarte (dh erfordere) ich, dass dies explizit dokumentiert wird. I'm curious why you expect that reference types won't define content equality.Sie definieren es nicht standardmäßig , aber das heißt nicht, dass es nicht möglich ist. Ich habe nie gesagt, dass dies nicht möglich ist (oder nicht möglich sein sollte), ich erwarte es einfach nicht (dh nehme es standardmäßig an).
Flater
Ich verstehe, was du meinst, danke für die Klarstellung.
Casablanca
2

Sie vergleichen im Grunde Zeichenfolgen, und ich wäre erstaunt, wenn zwei Wrapper, die denselben XML-Inhalt enthalten, nicht als gleich angesehen würden, sei es mit Equals oder ==.

Die idiomatische Regel kann für Referenztypobjekte im Allgemeinen sinnvoll sein, aber Zeichenfolgen sind im idiomatischen Sinne besonders. Sie sollten sie behandeln und als Werte betrachten, obwohl es sich technisch gesehen um Referenztypen handelt.

Ihr Wrapper-Postfix sorgt jedoch für zusätzliche Verwirrung. Grundsätzlich heißt es "kein XML-Element". Soll ich es also doch als Referenztyp behandeln? Semantisch wäre dies nicht sinnvoll. Ich wäre weniger verwirrt, wenn die Klasse den Namen XmlContent hätte. Dies würde bedeuten, dass wir uns um Inhalte und nicht um Details der technischen Implementierung kümmern.

Martin Maat
quelle
Wo würden Sie die Grenze zwischen "einem Objekt, das aus einer Zeichenfolge erstellt wurde" und "im Grunde einer Zeichenfolge" setzen? Aus der Perspektive der externen API scheint dies eine recht zweideutige Definition zu sein. Muss der Benutzer der API tatsächlich die Interna erraten, um das Verhalten zu erraten?
Arthur Havlicek
@Arthur Die Semantik der Klasse / des Objekts sollte den Hinweis liefern. Und manchmal ist es gar nicht so offensichtlich, daher diese Frage. Für echte Werttypen ist dies für jeden offensichtlich. Auch für echte Streicher. Der Typ sollte letztendlich angeben, ob ein Vergleich Inhalt oder Objektidentität beinhalten soll.
Martin Maat,