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.Equals
gdzie brak NotEquals
pokazów CPF, że ramy uważa !Equals()
obiektów jak nierówny i tyle. Ponadto klasy podobne Dictionary
i 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 Equals
metod. 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.
źródło
Odpowiedzi:
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:
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:
jest tak samo ważny jak ten:
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 #:
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ę:
dostaniesz
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
źródło
==
pojawia się pytanie, po co zwracać cokolwiek innego niżbool
? Jeśli zwróciszint
, nie możesz już używać go jako instrukcji warunkowej, np.if(a == b)
nie będzie się już kompilować. Jaki jest sens?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:
I
a == true
zwraca,false
aa == false
także zwracafalse
.źródło
a
nie jest to wartość logiczna, powinieneś porównać to z wyliczeniem trójstanowym, a nie rzeczywistymtrue
lubfalse
.a == true
jestfalse
to bym zawsze spodziewaća != true
siętrue
, pomimoa == false
byciafalse
==
, a nie dlaczego powinien być zmuszony do zastąpienia obu.!=
będzie poprawna. W niezwykle rzadkim przypadku, w którym chcielibyśmy,x == y
ax != y
jednocześ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!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
return
wyjść 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 samareturn
technika może mieć zastosowanie, kod może nie być tak ładny.źródło
!
blokuje cię w jednej definicji równości, gdzie świadomy programista może być w stanie zoptymalizować zarówno==
i!=
.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
i! = jak
Jeśli ty (lub kompilator, jeśli zrobił to domyślnie) miałbyś zaimplementować! = As
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ą.
źródło
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.
źródło
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ć.
źródło
Oto, co przychodzi mi do głowy:
false
oba produkty za==
!=
( i jeśli z jakiegoś powodu nie można ich porównać)źródło
Dictionary
iList
jeśli użyjesz z nimi swojego niestandardowego typu.Dictionary
używa,GetHashCode
aEquals
nie operatorów.List
wykorzystujeEquals
.Cóż, to prawdopodobnie tylko wybór projektu, ale jak mówisz,
x!= y
nie 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 ...
źródło
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
źródło
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 (
ref
zyski i mieszkańców, na przykład), a wiele przykładów realizacji funkcji nie w samym CLR (np:using
,lock
,foreach
itp).źródło
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
źródło
Null
stanowiłoby tylko problem, ponieważnull
jest 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.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.
źródło
operator ==
a kompilator by wywnioskowałoperator !=
. W końcu to właśnie robi dlaoperator +
ioperator +=
.a != b
jako!(a == b)
… (co nie robi C #).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.
źródło