Czy opakowania powinny być porównywane jako równe przy użyciu operatora ==, gdy zawijają ten sam obiekt?

19

Piszę opakowanie dla elementów XML, które pozwala programistom łatwo analizować atrybuty z XML. Opakowanie nie ma innego stanu niż zawijany obiekt.

Rozważam następującą implementację (uproszczoną w tym przykładzie), która obejmuje przeciążenie ==operatora.

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

Jak rozumiem idiomatyczne c #, ==operator służy do równości odniesienia, podczas gdy Equals()metoda służy do równości wartości. Ale w tym przypadku „wartość” jest tylko odniesieniem do opakowanego obiektu. Więc nie jestem jasne, co jest konwencjonalne lub idiomatyczne dla c #.

Na przykład w tym kodzie ...

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

.... czy program powinien wypisać „Opakowania aib są takie same”? Czy byłoby to dziwne, tj. Naruszałoby zasadę najmniejszego zdziwienia ?

John Wu
źródło
Przez cały czas przesadzam Equalsnigdy nie przesadzam ==(ale nigdy na odwrót). Czy leniwy idiomatyczny? Jeśli dostanę inne zachowanie bez wyraźnej obsady, która narusza najmniejsze zdziwienie.
radarbob
Odpowiedź na to zależy od tego, co robi NameAttribute - modyfikuje element podstawowy? Czy dodatkowa część danych? Znaczenie przykładowego kodu (i czy należy go uznać za równy) zmienia się w zależności od tego, więc myślę, że musisz go wypełnić.
Errorsatz
@Errorsatz Przepraszam, ale chciałem, aby przykład był zwięzły i przyjąłem, że było jasne, że zmodyfikuje on zawinięty element (w szczególności poprzez modyfikację atrybutu XML o nazwie „nazwa”). Ale specyfika nie ma znaczenia - chodzi o to, że że opakowanie umożliwia dostęp do odczytu / zapisu owiniętego elementu, ale nie zawiera własnego stanu.
John Wu
4
Cóż, w tym przypadku mają one znaczenie - oznacza to, że przypisanie „Cześć” i „Świat” jest mylące, ponieważ to drugie zastąpi poprzednie. Co oznacza, że ​​zgadzam się z odpowiedzią Martina, że ​​oba można uznać za równe. Gdyby NameAttribute faktycznie różniły się między nimi, nie uważałbym ich za równe.
Errorsatz
2
„Jak rozumiem idiomatyczny język c #, operator == służy do równości odniesienia, podczas gdy metoda Equals () służy do równości wartości.” Jest to chociaż? Większość przypadków, które widziałem == przeładowane, to równość wartości. Najważniejszym przykładem jest System.String.
Arturo Torres Sánchez

Odpowiedzi:

17

Ponieważ odniesienie do owinięcia XElementjest niezmienne, nie ma zewnętrznej obserwowalnej różnicy między dwoma wystąpieniami XmlWrappertego owinięcia tego samego elementu, więc sensowne jest przeciążenie, ==aby odzwierciedlić ten fakt.

Kod klienta prawie zawsze dba o logiczną równość (która domyślnie jest implementowana przy użyciu równości referencyjnej dla typów referencyjnych). Fakt, że na stercie znajdują się dwa wystąpienia, jest szczegółem implementacji, o który klienci nie powinni się przejmować (a ci, którzy to robią, będą używać Object.ReferenceEqualsbezpośrednio).

Casablanka
źródło
9

Jeśli uważasz, że ma to jak najbardziej sens

Pytanie i odpowiedź jest kwestią oczekiwań programistów , nie jest to wymóg techniczny.

JEŻELI uważasz, że opakowanie nie ma tożsamości i jest zdefiniowane wyłącznie na podstawie jego zawartości, odpowiedź na twoje pytanie brzmi „tak”.

Ale to jest powtarzający się problem. Czy dwa owijki powinny wykazywać równość, gdy owijają różne obiekty, ale oba obiekty mają dokładnie taką samą zawartość?

Odpowiedź się powtarza. JEŻELI obiekty treści nie mają żadnej tożsamości osobistej i zamiast tego są wyłącznie zdefiniowane przez ich treść, wówczas obiekty treści są faktycznie opakowaniami, które będą wykazywać równość. Jeśli następnie zawiniesz obiekty treści w inne opakowanie, to (dodatkowe) opakowanie powinno również wykazywać równość.

To żółwie aż do końca .


Ogólna wskazówka

Ilekroć odstępujesz od domyślnego zachowania, powinno to być wyraźnie udokumentowane. Jako programista oczekuję, że dwa typy referencyjne nie będą wykazywać równości, nawet jeśli ich zawartość będzie równa. Jeśli zmienisz to zachowanie, sugeruję wyraźne udokumentowanie go, aby wszyscy programiści byli świadomi tego nietypowego zachowania.


Jak rozumiem idiomatyczne c #, ==operator służy do równości odniesienia, podczas gdy Equals()metoda służy do równości wartości.

Takie jest jego domyślne zachowanie, ale nie jest to reguła niezmienna. Jest to kwestia konwencji, ale konwencje można zmienić tam, gdzie jest to uzasadnione .

stringjest tutaj świetnym przykładem, podobnie jak ==kontrola równości wartości (nawet gdy nie ma internowania ciągów!). Dlaczego? Mówiąc prosto: ponieważ ciągi zachowują się jak obiekty wartości, dla większości programistów są bardziej intuicyjne.

Jeśli twoją bazę kodu (lub życie programistów) można znacznie uprościć, sprawiając, że twoje opakowania wykazują równość wartości we wszystkich obszarach, idź do niej (ale udokumentuj ją ).

Jeśli nigdy nie potrzebujesz sprawdzania równości referencji (lub są one bezużyteczne w Twojej domenie biznesowej), nie ma sensu utrzymywać kontroli równości referencji. Lepiej jest zastąpić go sprawdzeniem równości wartości, aby uniknąć błędu programisty .
Jednak zdaj sobie sprawę, że jeśli potrzebujesz późniejszej kontroli równości odniesienia, jej ponowne wdrożenie może wymagać znacznego wysiłku.

Flater
źródło
Ciekawe, dlaczego oczekujesz, że typy referencyjne nie będą definiować równości treści. Większość typów nie definiuje równości tylko dlatego, że nie jest to niezbędne dla ich dziedziny, a nie dlatego, że chcą równości odniesienia.
casablanca
3
@casablanca: Myślę, że interpretujesz inną definicję „oczekiwania” (tj. wymaganie kontra założenie). Bez dokumentacji oczekuję (tj. Zakładam), że ==sprawdza równość referencji, ponieważ jest to zachowanie domyślne. Jeśli jednak ==faktycznie sprawdza równość wartości, oczekuję (tj. Wymagam), że jest to wyraźnie udokumentowane. DomyślnieI'm curious why you expect that reference types won't define content equality. tego nie definiują , ale to nie znaczy, że nie da się tego zrobić. Nigdy nie powiedziałem, że nie można (lub nie powinien) być zrobiony, po prostu nie oczekuję (tj. Zakładam) domyślnie.
Flater
Rozumiem, co masz na myśli, dzięki za wyjaśnienie.
casablanca
2

Zasadniczo porównujesz ciągi, więc byłbym zdziwiony, gdyby dwa opakowania zawierające tę samą treść XML nie były uważane za równe, niezależnie od tego, czy sprawdza się je za pomocą Równości czy ==.

Reguła idiomatyczna może mieć sens dla obiektów typu referencyjnego w ogóle, ale ciągi znaków są specjalne w sensie idiomatycznym, należy traktować je i traktować jako wartości, chociaż technicznie są to typy referencyjne.

Twój postfiks Wrapper dodaje zamieszania. Mówi w zasadzie „nie jest elementem XML”. Czy powinienem jednak traktować to jako typ referencyjny? Semantycznie nie miałoby to sensu. Byłbym mniej zdezorientowany, gdyby klasa miała nazwę XmlContent. Oznaczałoby to, że zależy nam na treści, a nie technicznych szczegółach implementacji.

Martin Maat
źródło
Gdzie umieściłbyś granicę między „obiektem zbudowanym z łańcucha” a „w zasadzie łańcuchem”? Z zewnętrznej perspektywy API wydaje się to dość niejednoznaczna. Czy użytkownik interfejsu API musi odgadnąć elementy wewnętrzne, aby odgadnąć zachowanie?
Arthur Havlicek
@Arthur Semantyka klasy / obiektu powinna stanowić wskazówkę. A czasem nie jest to takie oczywiste, stąd to pytanie. Dla prawdziwych typów wartości będzie to oczywiste dla każdego. Także dla prawdziwych strun. Typ powinien ostatecznie powiedzieć, czy porównanie powinno obejmować tożsamość treści czy obiektu.
Martin Maat,