Dlaczego (obiekt) 0 == (obiekt) 0 różni się od ((obiekt) 0) .Equals ((obiekt) 0)?


117

Dlaczego poniższe wyrażenia są różne?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

Właściwie mogę całkowicie zrozumieć [1], ponieważ prawdopodobnie środowisko uruchomieniowe .NET poda boxliczbę całkowitą i zamiast tego zacznie porównywać odwołania. Ale dlaczego [2] jest inny?


36
OK, teraz, gdy już rozumiesz odpowiedź na to pytanie, sprawdź swoje rozumienie, przewidując wynik: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Teraz porównaj to z rzeczywistością. Czy twoja prognoza była poprawna? Jeśli nie, czy możesz wyjaśnić tę rozbieżność?
Eric Lippert

1
@Star, aby zapoznać się z zalecanymi lekturami, zobacz msdn.microsoft.com/en-us/library/vstudio/…, aby zapoznać się z dostępnymi przeciążeniami w metodzie int16aka shortEquals, a następnie spójrz na msdn.microsoft.com/en-us/library/ms173105.aspx . Nie chcę zepsuć łamigłówki Erica Lipperta, ale po przeczytaniu tych stron powinno być całkiem łatwe do rozwiązania.
Sam Skuce

2
Myślałem, że to pytanie Java; przynajmniej przed zobaczeniem „E” w Równości.
seteropere

4
@seteropere Java jest w rzeczywistości inna: autoboxing w Javie buforuje obiekty, więc zwraca ((Integer)0)==((Integer)0)wartość true.
Jules,

1
Możesz też spróbować IFormattable x = 0; bool test = (object)x == (object)x;. Żadne nowe opakowanie nie jest wykonywane, gdy struktura jest już w pudełku.
Jeppe Stig Nielsen,

Odpowiedzi:


151

Powodem, dla którego wywołania zachowują się inaczej, jest to, że wiążą się z bardzo różnymi metodami.

==Przypadku będzie wiązać do statycznego operatora równości odniesienia. Istnieją 2 niezależne pudełkaintUtworzono wartości w dlatego nie są one tym samym odniesieniem.

W drugim przypadku łączysz się z metodą instancji Object.Equals. Jest to metoda wirtualna, która filtruje do Int32.Equalsi sprawdza liczbę całkowitą w pudełku. Obie wartości całkowite wynoszą 0, więc są równe


==Sprawa nie wymaga Object.ReferenceEquals. Po prostu tworzy ceqinstrukcję IL, aby wykonać porównanie referencyjne.
Sam Harwell,

8
@ 280Z28 czy to nie tylko dlatego, że kompilator to wstawia?
markmnl

@ 280Z28 Więc? Podobnym przypadkiem jest to, że ich metoda Boolean.ToString najwyraźniej zawiera zakodowane na stałe ciągi wewnątrz swojej funkcji, zamiast zwracać publicznie ujawnione wartości Boolean.TrueString i Boolean.FalseString. To nie ma znaczenia; Chodzi o to, że ==robi to samo, co ReferenceEquals(w każdym razie na Object). To tylko wewnętrzna optymalizacja po stronie MS, aby uniknąć niepotrzebnych wewnętrznych wywołań funkcji często używanych funkcji.
Nyerguds,

6
Specyfikacja języka C #, paragraf 7.10.6, mówi: Predefiniowane operatory równości typów referencyjnych to: bool operator ==(object x, object y); bool operator !=(object x, object y);Operatory zwracają wynik porównania dwóch odwołań dla równości lub nierówności. Nie jest wymagane, aby metoda System.Object.ReferenceEqualsbyła stosowana do określenia wyniku. To @markmnl: Nie, kompilator C # nie jest wbudowany, to jest coś, co czasami robi jitter (ale nie w tym przypadku). Więc 280Z28 ma rację, ReferenceEqualsmetoda nie jest faktycznie używana.
Jeppe Stig Nielsen,

@JaredPar: To interesujące, że specyfikacja tak mówi, ponieważ język tak naprawdę się nie zachowuje. Biorąc pod uwagę operatory zdefiniowane jak powyżej i zmienne Cat Whiskers; Dog Fido; IDog Fred;(dla niepowiązanych interfejsów ICati IDogoraz niepowiązanych klas Cat:ICati Dog:IDog), porównania Whiskers==Fidoi Whiskers==34byłyby legalne (pierwsze mogłoby być prawdziwe tylko wtedy, gdyby Wiskery i Fido były zerowe; drugie nigdy nie mogłoby być prawdziwe ). W rzeczywistości kompilator C # odrzuci oba. Whiskers==Fred;będzie zabronione, jeśli Catjest zapieczętowane, ale dozwolone, jeśli nie jest.
supercat

26

Podczas rzutowania wartości int 0(lub dowolnego innego typu wartości) objectna wartość jest umieszczana w ramce . Każde rzutowanie na objecttworzy inne pudełko (tj. Inną instancję obiektu). ==Operator dlaobject typu wykonuje porównawczych odniesienia, więc zwraca false, ponieważ po lewej stronie i po prawej stronie nie są takie same instancji.

Z drugiej strony, gdy używasz Equalsmetody wirtualnej, używa ona implementacji rzeczywistego typu pudełkowego, tj. Int32.EqualsZwracającego wartość true, ponieważ oba obiekty mają tę samą wartość.


18

==Operatora, będąc statycznej nie jest wirtualny. Uruchomi dokładnie ten kod, któryobject zdefiniowany klasę (`obiekt będący typem operandów w czasie kompilacji), który wykona porównanie referencji, niezależnie od typu środowiska wykonawczego któregokolwiek z obiektów.

EqualsMetodą jest metoda przykład wirtualny. Będzie uruchamiał kod zdefiniowany w rzeczywistym typie wykonawczym (pierwszego) obiektu, a nie kod w objectklasie. W tym przypadku obiektem jest an int, więc przeprowadzi porównanie wartości, ponieważ to inttyp definiuje dla swojej Equalsmetody.


W ==rzeczywistości token reprezentuje dwa operatory, z których jeden jest przeciążalny, a jeden nie. Zachowanie drugiego operatora bardzo różni się od zachowania przeciążenia (obiekt, obiekt).
supercat

13

Equals()Metoda jest wirtualny.
Dlatego zawsze wywołuje konkretną implementację, nawet gdy wywołanie witryny jest rzutowane object. intnadpisuje, Equals()aby porównać według wartości, aby uzyskać porównanie wartości.


10

== Posługiwać się: Object.ReferenceEquals

Object.Equals porównuje wartość.

Plik object.ReferenceEqualsSposób porównuje referencje. Podczas alokowania obiektu, oprócz danych obiektu na stercie pamięci, otrzymujesz odwołanie zawierające wartość wskazującą jego lokalizację w pamięci.

object.EqualsSposób porównuje zawartości przedmiotów. Najpierw sprawdza, czy odwołania są równe, podobnie jak object.ReferenceEquals. Ale potem wywołuje wyprowadzone metody Equals, aby dalej testować równość. Zobacz:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Chociaż Object.ReferenceEqualszachowuje się jak metoda, która używa ==operatora C # w swoich operandach, operator C # odwołania równości operatora (który jest reprezentowany przy użyciu ==typów operandów, dla których nie jest zdefiniowane przeciążenie) używa specjalnej instrukcji zamiast wywoływania ReferenceEquals. Co więcej, Object.ReferenceEqualszaakceptuje operandy, które mogą pasować tylko wtedy, gdy oba są zerowe, i zaakceptuje operandy, które muszą być wymuszone na typ, Objecta zatem nie mogą niczego dopasować, podczas gdy wersja równości odniesienia ==odmówiłaby kompilacji takiego użycia .
supercat

9

Operator C # używa tokenu ==do reprezentowania dwóch różnych operatorów: operatora porównania z możliwością przeciążenia statycznego i operatora porównania referencji, który nie jest przeciążalny. Kiedy napotyka ==token, najpierw sprawdza, czy istnieje jakiekolwiek przeciążenie testu równości, które ma zastosowanie do typów operandów. Jeśli tak, wywoła to przeciążenie. W przeciwnym razie sprawdzi, czy typy mają zastosowanie do operatora porównania odwołań. Jeśli tak, użyje tego operatora. Jeśli żaden operator nie ma zastosowania do typów operandów, kompilacja zakończy się niepowodzeniem.

Kod (Object)0nie tylko uskok Int32do Object: Int32podobnie jak wszystkich typów wartości, faktycznie reprezentuje dwa rodzaje, z których jeden opisano wartości i lokalizacje pamięci (takie jak dosłownym zero), ale nie wynika z niczego, a jeden z nich opisuje sterty obiektów i pochodzi z Object; ponieważ tylko ten drugi typ może być upcastowany do Object, kompilator musi utworzyć nowy obiekt sterty tego drugiego typu. Każde wywołanie (Object)0tworzy nowy obiekt sterty, więc dwa operandy ==są różnymi obiektami, z których każdy niezależnie hermetyzuje Int32wartość 0.

Klasa Objectnie ma żadnych użytecznych przeciążeń zdefiniowanych dla operatora równa się. W związku z tym kompilator nie będzie mógł użyć przeciążonego operatora testu równości i powróci do korzystania z testu równości odniesienia. Ponieważ dwa operandy ==odnoszą się do różnych obiektów, zgłosi false. Drugie porównanie kończy się powodzeniem, ponieważ pyta jedno wystąpienie obiektu sterty, Int32czy jest równe drugiemu. Ponieważ ta instancja wie, co to znaczy być równa innej odrębnej instancji, może odpowiedzieć true.


Dodatkowo, za każdym razem, gdy piszesz literał 0w swoim kodzie, zakładam, że tworzy on w tym celu obiekt int. Nie jest to unikalne odniesienie do jednej globalnej statycznej wartości zerowej (tak jak w przypadku tworzenia String.Empty, aby uniknąć tworzenia nowych pustych obiektów typu string tylko do inicjowania nowych ciągów) Więc jestem prawie pewien, że nawet wykonanie a 0.ReferenceEquals(0)zwróci false, ponieważ oba 0 są nowo utworzone Int32obiekty.
Nyerguds,

1
@Nyerguds, jestem prawie pewien, że wszystko, co powiedziałeś, jest niepoprawne, dotyczące ints, sterty, historii, globalnej statystyki itp. Zakończy się 0.ReferenceEquals(0)niepowodzeniem, ponieważ próbujesz wywołać metodę na stałej czasowej kompilacji. nie ma przedmiotu, aby go zawiesić. Unboxed int jest strukturą przechowywaną na stosie. Nawet int i = 0; i.ReferenceEquals(...)nie zadziała. Ponieważ System.Int32NIE dziedziczy z Object.
Andrew Backer,

@AndrewBacker, System.Int32to a struct, a structis System.ValueType, które samo dziedziczy System.Object. Dlatego jest ToString()metoda i EqualsmetodaSystem.Int32
Sebastian,

1
Niemniej jednak Nyerguds błędnie stwierdza, że ​​na stercie zostanie utworzony Int32, co nie jest prawdą.
Sebastian,

@SebastianGodelet, w pewnym sensie ignoruję w tym elementy wewnętrzne. Sam System.Int32 implementuje te metody. Funkcja GetType () jest włączona Objecti właśnie wtedy przestałem się tym martwić dawno temu. Nigdy nie trzeba było iść dalej. AFAIK CLR obsługuje te dwa typy inaczej i specjalnie. To nie tylko dziedziczenie. Jest to jednak isjeden z dwóch typów danych. Po prostu nie chciałem, aby ktokolwiek przeczytał ten komentarz i oddalił się tak daleko, łącznie z tą dziwnością dotyczącą pustych ciągów, która ignoruje ich internowanie.
Andrew Backer,

3

Oba testy są różne. Pierwsza sprawdza tożsamość , druga równość . Ogólnie dwa terminy są identyczne, jeśli odnoszą się do tego samego przedmiotu. Oznacza to, że są równi. Dwa wyrazy są równe, jeśli ich wartości są takie same.

W kategoriach programowania tożsamość jest zwykle zakłócana przez równość odniesień. Jeśli wskaźnik na oba wyrazy są równe (!), Obiekt, na który wskazują, jest dokładnie tym samym. Jeśli jednak wskaźniki są różne, wartość obiektów, na które wskazują, może być nadal równa. W języku C # tożsamość można sprawdzić przy użyciu statycznego Object.ReferenceEqualselementu członkowskiego, podczas gdy równość jest sprawdzana przy użyciu niestatycznego Object.Equalselementu członkowskiego. Ponieważ rzutujesz dwie liczby całkowite na obiekty (co jest nazywane „boksowaniem”, btw), operator ==funkcji objectwykonuje pierwsze sprawdzenie, które jest domyślnie odwzorowywane na Object.ReferenceEqualsi sprawdza tożsamość. Jeśli bezpośrednio wywołasz element niestatyczny Equals, dynamiczne wysyłanie spowoduje wywołanie funkcji Int32.Equals, które sprawdza równość.

Obie koncepcje są podobne, ale nie takie same. Na początku mogą wydawać się zagmatwane, ale mała różnica jest bardzo ważna! Wyobraź sobie dwie osoby, a mianowicie „Alice” i „Bob”. Oboje mieszkają w żółtym domu. Wychodząc z założenia, że ​​Alicja i Bob mieszkają w dzielnicy, w której domy różnią się tylko kolorem, oboje mogliby mieszkać w różnych żółtych domach. Jeśli porównasz oba domy, zauważysz, że są one absolutnie takie same, ponieważ oba są żółte! Jednak nie dzielą tego samego domu i dlatego ich domy są równe , ale nie identyczne . Tożsamość sugerowałaby, że mieszkają w tym samym domu.

Uwaga : niektóre języki definiują ===operatora do sprawdzania tożsamości.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.