Dlaczego musimy zdefiniować zarówno == i! = W języku C #?

346

Kompilator C # wymaga, aby ilekroć typ niestandardowy definiował operatora ==, musi on także definiować !=(patrz tutaj ).

Dlaczego?

Jestem ciekawy, dlaczego projektanci uznali to za konieczne i dlaczego kompilator nie może ustawić domyślnej rozsądnej implementacji dla jednego z operatorów, gdy obecny jest tylko drugi. Na przykład Lua pozwala zdefiniować tylko operator równości, a drugi dostajesz za darmo. C # może zrobić to samo, prosząc o zdefiniowanie == lub oba == i! =, A następnie automatycznie skompiluje brakujący operator! = Jako !(left == right).

Rozumiem, że istnieją dziwne przypadki narożne, w których niektóre byty nie mogą być ani równe, ani nierówne (jak IEEE-754 NaN), ale te wydają się być wyjątkiem, a nie regułą. To nie wyjaśnia, dlaczego projektanci kompilatora C # uczynili wyjątek regułą.

Widziałem przypadki słabego wykonania, w których zdefiniowano operator równości, a następnie operator nierówności jest kopiowaniem i wklejaniem z każdym odwróconym porównaniem i każdym przełączeniem && na || (dostajesz punkt ... w zasadzie! (a == b) rozwinął się według zasad De Morgana). Jest to zła praktyka, którą kompilator może wyeliminować z założenia, tak jak w przypadku Lua.

Uwaga: to samo dotyczy operatorów <> <=> =. Nie wyobrażam sobie przypadków, w których będziesz musiał zdefiniować je w nienaturalny sposób. Lua pozwala definiować tylko <i <= i definiuje> = i> naturalnie poprzez negację formatorów. Dlaczego C # nie robi tego samego (przynajmniej „domyślnie”)?

EDYTOWAĆ

Najwyraźniej istnieją ważne powody, aby pozwolić programiście na wdrożenie kontroli równości i nierówności w dowolny sposób. Niektóre odpowiedzi wskazują na przypadki, w których może to być miłe.

Jądro mojego pytania brzmi jednak, dlaczego jest to wymuszone w C #, skoro zwykle nie jest to logicznie konieczne?

Jest również w uderzający kontrast do projektowania wyborów dla .NET interfejsy jak Object.Equals, IEquatable.Equals IEqualityComparer.Equalsgdzie brak NotEqualspokazów CPF, że ramy uważa !Equals()obiektów jak nierówny i tyle. Ponadto klasy podobne Dictionaryi metody .Contains()zależą wyłącznie od wyżej wymienionych interfejsów i nie używają operatorów bezpośrednio, nawet jeśli są zdefiniowane. W rzeczywistości, gdy ReSharper generuje członków równości, określa zarówno ==i !=pod względem, Equals()a nawet wtedy, gdy użytkownik zdecyduje się generować operatory w ogóle. Operatory równości nie są potrzebne ramom do zrozumienia równości obiektów.

Zasadniczo środowisko .NET nie dba o tych operatorów, obchodzi je tylko kilka Equalsmetod. Decyzja o wymaganiu, aby zarówno operator ==, jak i! = Były definiowane przez użytkownika w tandemie, jest związana wyłącznie z projektem języka, a nie semantyką obiektową w zakresie .NET.

Stefan Dragnev
źródło
24
+1 za doskonałe pytanie. Wydaje się, że nie ma żadnego powodu ... oczywiście kompilator może założyć, że a! = B można przetłumaczyć na! (A == b), chyba że powiedziano inaczej. Ciekaw jestem, dlaczego też postanowili to zrobić.
Patrick87,
16
To jest najszybsze, jakie widziałem, kiedy głosowałem i faworyzowałem pytanie.
Christopher Currens
26
Jestem pewien, że @Eric Lippert również może udzielić solidnej odpowiedzi.
Fuj
17
„Naukowiec nie jest osobą, która udziela właściwych odpowiedzi, jest tym, który zadaje właściwe pytania”. Dlatego w SE zachęca się do zadawania pytań. I to działa!
Adriano Carneiro,
4
To samo pytanie dla Pythona: stackoverflow.com/questions/4969629/…
Josh Lee

Odpowiedzi:

161

Nie mogę mówić za projektantami języka, ale z tego, co rozumiem, wydaje się, że była to celowa, właściwa decyzja projektowa.

Patrząc na ten podstawowy kod F #, możesz skompilować go w działającej bibliotece. To jest legalny kod dla F # i przeciąża tylko operatora równości, a nie nierówności:

module Module1

type Foo() =
    let mutable myInternalValue = 0
    member this.Prop
        with get () = myInternalValue
        and set (value) = myInternalValue <- value

    static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
    //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

Robi to dokładnie tak, jak wygląda. Tworzy tylko moduł porównujący równość ==i sprawdza, czy wewnętrzne wartości klasy są równe.

Chociaż nie można utworzyć takiej klasy w języku C #, można użyć klasy skompilowanej dla platformy .NET. To oczywiste, że użyje naszego przeciążonego operatora. == Więc do czego służy środowisko wykonawcze !=?

Standard C # EMCA ma całą masę zasad (sekcja 14.9) wyjaśniających, jak określić, którego operatora należy użyć podczas oceny równości. Mówiąc wprost, zbyt uproszczone, a przez to nie do końca dokładne, jeśli porównywane typy są tego samego typu i występuje przeciążony operator równości, użyje tego przeciążenia, a nie standardowego operatora równości odniesienia odziedziczonego z Object. Nic więc dziwnego, że jeśli obecny jest tylko jeden operator, użyje domyślnego operatora równości odniesienia, który mają wszystkie obiekty, nie ma dla niego przeciążenia. 1

Wiedząc, że tak jest, prawdziwe pytanie brzmi: dlaczego zostało to zaprojektowane w ten sposób i dlaczego kompilator sam tego nie odkryje? Wiele osób twierdzi, że nie była to decyzja projektowa, ale lubię myśleć, że zostało to przemyślane w ten sposób, zwłaszcza biorąc pod uwagę fakt, że wszystkie obiekty mają domyślny operator równości.

Dlaczego więc kompilator nie tworzy automatycznie !=operatora? Nie wiem na pewno, chyba że ktoś z Microsoftu to potwierdzi, ale to mogę ustalić na podstawie uzasadnienia faktów.


Aby zapobiec nieoczekiwanemu zachowaniu

Być może chcę zrobić porównanie wartości, ==aby sprawdzić równość. Jednak, gdy doszło do !=tego, nie obchodziło mnie, czy wartości są równe, chyba że referencje są równe, ponieważ dla mojego programu, aby uznać je za równe, dbam tylko o to, czy referencje są zgodne. W końcu jest to przedstawione jako domyślne zachowanie C # (gdyby oba operatory nie były przeciążone, tak jak w przypadku niektórych bibliotek .net napisanych w innym języku). Jeśli kompilator dodawał kod automatycznie, nie mogłem już polegać na kompilatorze, aby kod wyjściowy był zgodny. Kompilator nie powinien pisać ukrytego kodu, który zmienia twoje zachowanie, szczególnie gdy kod, który napisałeś, jest zgodny zarówno ze standardem C #, jak i CLI.

Jeśli chodzi o to, że zmusza cię do przeciążenia, zamiast przejść do domyślnego zachowania, mogę tylko stanowczo powiedzieć, że jest to standardowe (EMCA-334 17.9.2) 2 . Standard nie precyzuje dlaczego. Wierzę, że wynika to z faktu, że C # zapożycza wiele zachowań od C ++. Zobacz więcej na ten temat.


Po zastąpieniu !=i ==nie musisz zwracać wartości bool.

To kolejny prawdopodobny powód. W języku C # ta funkcja:

public static int operator ==(MyClass a, MyClass b) { return 0; }

jest tak samo ważny jak ten:

public static bool operator ==(MyClass a, MyClass b) { return true; }

Jeśli zwracasz coś innego niż bool, kompilator nie może automatycznie wywnioskować przeciwnego typu. Ponadto, w przypadku, gdy operator dokłada powrotną bool, to po prostu nie ma sensu dla nich stworzyć wygenerować kod, który istnieje tylko w tym jednym konkretnym przypadku lub, jak powiedziałem wyżej kod, który ukrywa domyślne zachowanie CLR.


C # dużo pożyczy od C ++ 3

Kiedy wprowadzono C #, w magazynie MSDN pojawił się artykuł mówiący o C #:

Wielu programistów życzy sobie, aby istniał język, który można łatwo pisać, czytać i utrzymywać jak Visual Basic, ale nadal zapewnia on moc i elastyczność C ++.

Tak, celem projektu dla C # było zapewnienie prawie takiej samej mocy jak C ++, poświęcając tylko trochę dla udogodnień takich jak sztywne bezpieczeństwo typu i zbieranie śmieci. C # był silnie wzorowany na C ++.

Nie możesz być zaskoczony, gdy dowiesz się, że w C ++ operatory równości nie muszą zwracać wartości bool , jak pokazano w tym przykładowym programie

Teraz C ++ nie wymaga bezpośredniego przeciążania operatora uzupełniającego. Jeśli skompilowałeś kod w przykładowym programie, zobaczysz, że działa bez żadnych błędów. Jeśli jednak spróbujesz dodać linię:

cout << (a != b);

dostaniesz

błąd kompilatora C2678 (MSVC): binarny „! =”: nie znaleziono operatora, który pobierałby lewy operand typu „Test” (lub nie ma akceptowalnej konwersji).

Tak więc, chociaż sam C ++ nie wymaga przeciążania parami, nie pozwoli ci użyć operatora równości, którego nie przeciążyłeś w klasie niestandardowej. Jest poprawny w .NET, ponieważ wszystkie obiekty mają domyślny; C ++ nie.


1. Na marginesie, standard C # nadal wymaga przeciążenia pary operatorów, jeśli chcesz przeciążyć jeden z nich. Jest to część standardu, a nie tylko kompilatora . Jednak te same zasady dotyczące określania, który operator ma zadzwonić, mają zastosowanie podczas uzyskiwania dostępu do biblioteki .net napisanej w innym języku, który nie ma takich samych wymagań.

2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )

3. I Java, ale tak naprawdę nie o to chodzi

Christopher Currens
źródło
75
nie musisz wracać bool ” .. Myślę, że to nasza odpowiedź właśnie tam; dobra robota. Jeśli! (O1 == o2) nie jest nawet dobrze zdefiniowany, nie można oczekiwać, że standard będzie na nim polegał.
Brian Gordon
10
Jak na ironię w temacie o operatorach logicznych użyłeś podwójnego negatywu w zdaniu: „C # nie pozwala na przesłonięcie tylko jednego z nich”, co w rzeczywistości oznacza logicznie, że „C # pozwala na przesłonięcie tylko jednego z nich”.
Dan Diplo,
13
@Dan Diplo, w porządku, to nie tak, że nie mam zakazu, aby tego nie robić, czy nie.
Christopher Currens
6
Jeśli to prawda, podejrzewam, że nr 2 jest poprawny; ale teraz ==pojawia się pytanie, po co zwracać cokolwiek innego niż bool? Jeśli zwrócisz int, nie możesz już używać go jako instrukcji warunkowej, np. if(a == b)nie będzie się już kompilować. Jaki jest sens?
BlueRaja - Danny Pflughoeft
2
Możesz zwrócić obiekt, który ma niejawną konwersję na bool (operator prawda / fałsz), ale nadal nie widzę, gdzie byłoby to przydatne.
Stefan Dragnev
53

Prawdopodobnie jeśli ktoś musi wdrożyć logikę trójwartościową (tj null.). W takich przypadkach - na przykład w standardzie ANSI SQL - operatory nie mogą być po prostu negowane w zależności od danych wejściowych.

Możesz mieć przypadek, w którym:

var a = SomeObject();

I a == truezwraca, falsea a == falsetakże zwraca false.

Fuj
źródło
1
Powiedziałbym w takim przypadku, ponieważ anie jest to wartość logiczna, powinieneś porównać to z wyliczeniem trójstanowym, a nie rzeczywistym truelub false.
Brian Gordon,
18
Nawiasem mówiąc, nie mogę uwierzyć, że nikt nie odniósł się do żartu FileNotFound ..
Brian Gordon
28
Jeśli a == truejest falseto bym zawsze spodziewać a != truesię true, pomimo a == falsebyciafalse
Fede
20
@Fede uderzył w paznokieć w głowę. To naprawdę nie ma nic wspólnego z pytaniem - wyjaśniasz, dlaczego ktoś może chcieć zastąpić ==, a nie dlaczego powinien być zmuszony do zastąpienia obu.
BlueRaja - Danny Pflughoeft
6
@ Yuck - Tak, istnieje dobra domyślna implementacja: jest to częsty przypadek. Nawet w twoim przykładzie domyślna implementacja !=będzie poprawna. W niezwykle rzadkim przypadku, w którym chcielibyśmy, x == ya x != yjednocześnie obaj jesteśmy fałszywi (nie mogę wymyślić żadnego sensownego przykładu) , nadal mogą zastąpić domyślną implementację i podać własną. Zawsze ustaw domyślny przypadek!
BlueRaja - Danny Pflughoeft
25

Poza tym, że C # odsuwa się na C ++ w wielu obszarach, najlepszym wyjaśnieniem, jakie mogę wymyślić, jest to, że w niektórych przypadkach możesz chcieć zastosować nieco inne podejście do udowodnienia „nie równości” niż udowodnienia „równości”.

Oczywiście, na przykład przy porównywaniu ciągów, możesz po prostu przetestować równość i returnwyjść z pętli, gdy zobaczysz niepasujące znaki. Jednak może nie być tak czysty z bardziej skomplikowanymi problemami. Filtr kwitną przychodzi do głowy; bardzo łatwo jest szybko stwierdzić, czy elementu nie ma w zestawie, ale trudno stwierdzić, czy elementu jest w zestawie. Chociaż ta sama returntechnika może mieć zastosowanie, kod może nie być tak ładny.

Brian Gordon
źródło
6
Świetny przykład; Myślę, że rozumiesz dlaczego. Prosty !blokuje cię w jednej definicji równości, gdzie świadomy programista może być w stanie zoptymalizować zarówno == i !=.
Fuj
13
To wyjaśnia, dlaczego powinni mieć możliwość zastąpienia obu, a nie dlaczego powinni być do tego zmuszeni .
BlueRaja - Danny Pflughoeft
1
W tym duchu nie muszą optymalizować obu, muszą jedynie podać jasną definicję obu.
Jesper
22

Jeśli spojrzysz na implementacje przeciążeń == i! = W źródle .net, często nie implementują one! = As! (Lewy == prawy). Wdrażają go w pełni (jak ==) z zaprzeczoną logiką. Na przykład DateTime implementuje == as

return d1.InternalTicks == d2.InternalTicks;

i! = jak

return d1.InternalTicks != d2.InternalTicks;

Jeśli ty (lub kompilator, jeśli zrobił to domyślnie) miałbyś zaimplementować! = As

return !(d1==d2);

wtedy przyjmujesz założenie dotyczące wewnętrznej implementacji == i! = w rzeczach, do których odwołuje się twoja klasa. Unikanie tego założenia może być filozofią stojącą za ich decyzją.

siekiera - wykonane za pomocą SOverflow
źródło
17

Aby odpowiedzieć na swoją zmianę dotyczącą tego, dlaczego jesteś zmuszony zastąpić oba, jeśli zastąpisz jedno, wszystko jest w spadku.

Jeśli zastąpisz ==, najprawdopodobniej zapewnisz pewną równość semantyczną lub strukturalną (na przykład DateTimes są równe, jeśli ich właściwości InternalTicks są równe, nawet jeśli mogą to być różne wystąpienia), wówczas zmieniasz domyślne zachowanie operatora z Obiekt, który jest rodzicem wszystkich obiektów .NET. Operator == jest w języku C # metodą, której podstawowa implementacja Object.operator (==) wykonuje porównanie referencyjne. Object.operator (! =) To kolejna inna metoda, która wykonuje również porównanie referencyjne.

W prawie każdym innym przypadku zastąpienia metody nielogiczne byłoby zakładanie, że zastąpienie jednej metody spowoduje również zmianę behawioralną na metodę antonimiczną. Jeśli utworzyłeś klasę za pomocą metod Increment () i Decrement () i przesłoniłeś Increment () w klasie podrzędnej, czy spodziewałbyś się, że funkcja Decrement () również zostanie zastąpiona odwrotnością twojego nadpisanego zachowania? Kompilator nie może być wystarczająco inteligentny, aby wygenerować funkcję odwrotną dla dowolnej implementacji operatora we wszystkich możliwych przypadkach.

Jednak operatorzy, choć wdrażani bardzo podobnie do metod, koncepcyjnie pracują w parach; == i! =, <i> oraz <= i> =. Byłoby w tym przypadku nielogiczne z punktu widzenia konsumenta myśleć, że! = Działał inaczej niż ==. Tak więc kompilatora nie można założyć, że a! = B ==! (A == b) we wszystkich przypadkach, ale ogólnie oczekuje się, że == i! = Powinny działać w podobny sposób, więc kompilator wymusza wdrażacie parami, ale tak naprawdę to robicie. Jeśli dla twojej klasy a! = B ==! (A == b), po prostu zaimplementuj operator! = Za pomocą! (==), ale jeśli reguła ta nie obowiązuje dla wszystkich obiektów (na przykład , jeśli porównanie z określoną wartością, równą lub nierówną, nie jest prawidłowe), musisz być mądrzejszy niż IDE.

PRAWDZIWE pytanie, które należy zadać, brzmi: dlaczego <i> oraz <= i> = są parami dla operatorów porównawczych, które muszą być implementowane jednocześnie, gdy w kategoriach liczbowych! (A <b) == a> = b i! (A> b) == a <= b. Powinieneś być zobowiązany do implementacji wszystkich czterech, jeśli przesłonisz jeden, i prawdopodobnie powinieneś być również zobowiązany do zastąpienia == (i! =), Ponieważ (a <= b) == (a == b) jeśli a jest semantyczne równy b.

KeithS
źródło
14

Jeśli przeciążasz == dla swojego typu niestandardowego, a nie! =, Zostanie to obsłużone przez!! = Operator dla obiektu! = Obiekt, ponieważ wszystko pochodzi z obiektu, a to znacznie różni się od CustomType! = CustomType.

Również twórcy języków prawdopodobnie chcieli tego w ten sposób, aby umożliwić programistom jak największą elastyczność, a także, aby nie zakładali, co zamierzają zrobić.

Chris Mullins
źródło
Zainspirowałeś to pytanie: stackoverflow.com/questions/6919259/...
Brian Gordon
2
Przyczynę elastyczności można również zastosować do wielu funkcji, które postanowiono pominąć, na przykład blogs.msdn.com/b/ericlippert/archive/2011/06/23/ .... To nie wyjaśnia ich wyboru wdrożenie operatorów równości.
Stefan Dragnev,
To bardzo interesujące. Wydaje się, że byłaby to przydatna funkcja. Nie mam pojęcia, dlaczego mieliby to pominąć.
Chris Mullins,
11

Oto, co przychodzi mi do głowy:

  • Co jeśli testowanie nierówności jest znacznie szybsze niż testowanie równości?
  • Co jeśli w niektórych przypadkach chcesz zwrócić falseoba produkty za==!= ( i jeśli z jakiegoś powodu nie można ich porównać)
Dan Abramov
źródło
5
W pierwszym przypadku można następnie wdrożyć testowanie równości pod względem testowania nierówności.
Stefan Dragnev
Drugi przypadek spowoduje uszkodzenie struktur takich jak Dictionaryi Listjeśli użyjesz z nimi swojego niestandardowego typu.
Stefan Dragnev
2
@Stefan: Dictionaryużywa, GetHashCodea Equalsnie operatorów. Listwykorzystuje Equals.
Dan Abramov,
6

Cóż, to prawdopodobnie tylko wybór projektu, ale jak mówisz, x!= ynie musi być taki sam jak !(x == y). Nie dodając domyślnej implementacji, masz pewność, że nie możesz zapomnieć o implementacji konkretnej implementacji. A jeśli jest to tak trywialne, jak mówisz, możesz po prostu zaimplementować jedno za pomocą drugiego. Nie rozumiem, jak to jest „zła praktyka”.

Mogą być też inne różnice między C # i Lua ...

GolezTrol
źródło
1
Jest to doskonała praktyka w zakresie wdrażania jednego pod względem drugiego. Właśnie widziałem, że robi się inaczej - co jest podatne na błędy.
Stefan Dragnev
3
To jest problem programisty, a nie problem C #. Programiści, którzy nie wdrożą tego prawa, poniosą porażkę także w innych obszarach.
GolezTrol
@GolezTrol: Czy możesz wyjaśnić odniesienie do Lua? Lua wcale mnie nie zna, ale twoje odniesienie wydaje się coś sugerować.
zw324,
@ Ziyao Wei: OP wskazuje, że Lua robi to inaczej niż C # (Lua najwyraźniej dodaje domyślne odpowiedniki dla wspomnianych operatorów). Próbuję tylko trywializować takie porównanie. Jeśli w ogóle nie ma różnic, to przynajmniej jedna z nich nie ma prawa istnieć. Muszę powiedzieć, że też nie znam Lui.
GolezTrol
6

Kluczowymi słowami w twoim pytaniu są „ dlaczego ” i „ musisz ”.

W rezultacie:

Odpowiedź w ten sposób, ponieważ tak zaprojektowali, jest prawdą ... ale nie odpowiada na pytanie „dlaczego”.

Odpowiedź, że czasem może być pomocne zastąpienie obu z nich niezależnie, jest prawdą ... ale nie udzielenie odpowiedzi w części „obowiązkowej” pytania.

Myślę, że prosta odpowiedź jest taka, że ​​nie ma żadnego przekonującego powodu, dla którego C # wymaga, abyś zastąpił oba.

Język powinien pozwalają tylko przesłonić ==i dostarczyć implementację domyślnej !=to jest !to. Jeśli zdarzy ci się również chcieć przesłonić !=, zrób to.

To nie była dobra decyzja. Ludzie projektują języki, ludzie nie są doskonali, C # nie jest idealny. Wzruszyć ramionami i QED

Greg Hendershott
źródło
5

Aby dodać doskonałe odpowiedzi tutaj:

Zastanów się, co stałoby się w debuggerze , gdy próbujesz wejść do !=operatora i ==zamiast tego zostać operatorem! Mów o zamieszaniu!

Ma sens, że CLR pozwoli ci na swobodne pomijanie jednego lub drugiego operatora - ponieważ musi działać w wielu językach. Ale istnieje wiele przykładów C # nie narażając możliwości CLR ( refzyski i mieszkańców, na przykład), a wiele przykładów realizacji funkcji nie w samym CLR (np: using, lock, foreachitp).

Andrew Russell
źródło
3

Języki programowania to zmiany składniowe wyjątkowo złożonej instrukcji logicznej. Mając to na uwadze, czy możesz zdefiniować przypadek równości bez definiowania przypadku braku równości? Odpowiedź brzmi nie. Aby obiekt a był równy obiektowi b, wówczas odwrotność obiektu a nie równa się b również musi być prawdziwa. Innym sposobem na pokazanie tego jest

if a == b then !(a != b)

zapewnia to konkretną zdolność języka do określania równości obiektów. Na przykład porównanie NULL! = NULL może wrzucić klucz do definicji systemu równości, który nie implementuje instrukcji nierówności.

Teraz, jeśli chodzi o ideę! = Po prostu bycie wymienną definicją jak w

if !(a==b) then a!=b

Nie mogę się z tym kłócić. Jednak najprawdopodobniej grupa specyfikacji języka C # zdecydowała, że ​​programiści zostaną zmuszeni do jawnego zdefiniowania równości i nierówności obiektu razem

Josh
źródło
Nullstanowiłoby tylko problem, ponieważ nulljest to prawidłowy wkład, do ==którego nie powinno być, choć oczywiście jest to niezwykle wygodne. Osobiście uważam, że pozwala to na wiele niejasnych, niechlujnych programów.
nicodemus13
2

Krótko mówiąc, wymuszona spójność.

„==” i „! =” są zawsze prawdziwymi przeciwieństwami, bez względu na to, jak je zdefiniujesz, zdefiniowane jako takie przez ich słowną definicję „równa się” i „nie równa się”. Definiując tylko jeden z nich, otwierasz się na niespójność operatora równości, w której zarówno „==”, jak i „! =” Mogą być prawdziwe lub oba mogą być fałszywe dla dwóch podanych wartości. Musisz zdefiniować oba, ponieważ kiedy zdecydujesz się zdefiniować jedno, musisz także odpowiednio zdefiniować drugie, aby było jasne, jaka jest twoja definicja „równości”. Innym rozwiązaniem kompilatora jest umożliwienie jedynie przesłonięcia '==' OR '! =' I pozostawienie drugiego jako nieodłącznie negującego inne. Oczywiście nie jest tak w przypadku kompilatora C # i jestem tego pewien ”

Pytanie, które powinieneś zadać, brzmi „dlaczego muszę zastąpić operatorów?” Jest to silna decyzja, która wymaga silnego uzasadnienia. Dla obiektów „==” i „! =” Porównaj przez odniesienie. Jeśli chcesz je przesłonić, aby NIE porównywać przez odniesienie, tworzysz ogólną niespójność operatora, która nie jest widoczna dla żadnego innego programisty, który mógłby przejrzeć ten kod. Jeśli próbujesz zadać pytanie „czy stan tych dwóch instancji jest równoważny?”, Powinieneś zaimplementować IEquiable, zdefiniować Equals () i wykorzystać to wywołanie metody.

Wreszcie IEquatable () nie definiuje NotEquals () z tego samego rozumowania: potencjał do ujawnienia niespójności operatora równości. NotEquals () powinien ZAWSZE zwracać! Equals (). Otwierając definicję NotEquals () dla klasy implementującej Equals (), ponownie wymuszasz kwestię spójności w określaniu równości.

Edycja: To po prostu moje rozumowanie.

Emoire
źródło
To jest nielogiczne. Gdyby przyczyną była spójność, wówczas obowiązywałoby coś przeciwnego: można by tylko wdrożyć, operator ==a kompilator by wywnioskował operator !=. W końcu to właśnie robi dla operator +i operator +=.
Konrad Rudolph
1
foo + = pasek; to zadanie złożone, skrót dla foo = foo + bar ;. To nie jest operator.
blenderfreaky
Jeśli rzeczywiście są one „zawsze prawdziwymi przeciwieństwami”, byłby to powód do automatycznego zdefiniowania a != bjako !(a == b)… (co nie robi C #).
andrewf
-3

Prawdopodobnie po prostu coś, o czym nie pomyśleli, nie miało czasu.

Zawsze używam twojej metody, kiedy przeciążam ==. Potem używam tego w drugim.

Masz rację, przy niewielkiej ilości pracy kompilator może nam to dać za darmo.

Rhyous
źródło