Jaka jest różnica między == a Equals () dla prymitywów w C #?

180

Rozważ ten kod:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Zarówno inti shortsą prymitywne typy, ale porównanie z ==Zwraca true i porównanie z Equalszwraca false.

Czemu?

Mohammad Zargarani
źródło
9
@OrangeDog proszę pomyśleć o pytaniu, a następnie głosować na bliskim
4
Brakuje oczywistej próby odwrotnej:Console.WriteLine(age.Equals(newAge));
ANeves
3
Duplikat nie wyjaśnia tego zachowania; chodzi tylko o to, co Equals()ogólnie.
SLaks
37
Odpowiedziałem dokładnie na to pytanie na blogu Coverity kilka dni temu. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert
5
@CodesInChaos: Specyfikacja faktycznie używa terminu „typy pierwotne” dwa razy, nigdy go nie definiując; implikacja jest taka, że ​​pierwotne typy są wbudowanymi typami wartości, ale nigdy nie jest to wyjaśnione. Poleciłem Madsowi, aby termin ten został po prostu wykreślony ze specyfikacji, ponieważ wydaje się, że powoduje więcej zamieszania niż go usuwa.
Eric Lippert,

Odpowiedzi:

262

Krótka odpowiedź:

Równość jest skomplikowana.

Szczegółowa odpowiedź:

Typy operacji podstawowych zastępują bazę object.Equals(object)i zwracają wartość true, jeśli pole objectjest tego samego typu i wartości. (Należy pamiętać, że będzie to również działać dla typów zerowalnych; typy zerowe zerowalne zawsze są zaznaczone dla instancji typu bazowego).

Ponieważ newAgejest to short, jego Equals(object)metoda zwraca wartość true tylko wtedy, gdy przekażesz krótkie pole o tej samej wartości. Podajesz pudełko int, więc zwraca wartość false.

Natomiast ==operator definiuje się jako przyjmowanie dwóch ints (lub shorts lub longs).
Po wywołaniu go za pomocą intai shortkompilator domyślnie przekonwertuje wartość shortna inti porówna wynikowe intwartości s.

Inne sposoby, aby to działało

Typy pierwotne mają również własną Equals()metodę, która akceptuje ten sam typ.
Jeśli napiszesz age.Equals(newAge), kompilator wybierze int.Equals(int)najlepsze przeciążenie i domyślnie przekonwertuje shortna int. Następnie powróci true, ponieważ ta metoda po prostu porównuje ints bezpośrednio.

shortma również short.Equals(short)metodę, ale intnie można jej niejawnie przekonwertować short, więc jej nie wywołujesz.

Możesz zmusić go do wywołania tej metody za pomocą obsady:

Console.WriteLine(newAge.Equals((short)age)); // true

To zadzwoni short.Equals(short)bezpośrednio, bez boksu. Jeśli agejest większy niż 32767, wygeneruje wyjątek przepełnienia.

Można również wywołać short.Equals(object)przeciążenie, ale jawnie przekazać obiekt w ramce, aby uzyskał ten sam typ:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Podobnie jak poprzednia alternatywa, spowoduje to przepełnienie, jeśli nie pasuje do short. W przeciwieństwie do poprzedniego rozwiązania umieści go shortw obiekcie, marnując czas i pamięć.

Kod źródłowy:

Oto obie Equals()metody z rzeczywistego kodu źródłowego:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Dalsza lektura:

Zobacz Eric Lippert .

SLaks
źródło
3
@SLaks, jeśli zadzwonimy long == int, intdomyślnie przekonwertowane na longprawo?
Selman Genç
1
I tak, wszystko to napisałem, nie próbując tego.
SLaks
1
Pamiętaj, że w kodzie pytanie, czy jeden zmienia int age = 25;się const int age = 25;, wtedy wynik będzie się zmieniać. Jest tak, ponieważ w tym przypadku istnieje domniemana konwersja z intna short. Zobacz Implikowane konwersje wyrażeń stałych .
Jeppe Stig Nielsen
2
@SLaks tak, ale sformułowanie Twojej odpowiedzi „przekazana wartość” może być interpretowane na dwa sposoby (jako wartość przekazywana przez programistę lub wartość faktycznie przekazywana przez CLR po rozpakowaniu). Zgaduję, że przypadkowy użytkownik, który jeszcze nie zna odpowiedzi, przeczyta go jako poprzedni
JaredPar
2
@Rachel: Tyle że to nieprawda; domyślny == operator porównuje typy referencyjne przez odniesienie. W przypadku typów wartości i typów, które przeciążają ==, nie działa.
SLaks
55

Ponieważ nie ma przeciążenia, short.Equalsktóre akceptuje int. Dlatego nazywa się to:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objnie jest short… dlatego jest fałszywe.

Simon Whitehead
źródło
12

Kiedy przechodzisz intdo short's Equals, przekazujesz object:

wprowadź opis zdjęcia tutaj Działa więc ten pseudokod:

return obj is short && this == (short)obj;
Majid
źródło
10

==służy do sprawdzania równego warunku, może być traktowany jako operator (operator logiczny), po prostu do porównania 2 rzeczy, a tutaj typ danych nie ma znaczenia, ponieważ wykonano by rzutowanie typu i Equalsjest również używany do sprawdzania równego warunku , ale w tym przypadku typy danych powinny być takie same. N Równa się to metoda, a nie operator.

Poniżej znajduje się mały przykład wzięty z tego, który podałeś, i to w skrócie wyjaśni różnicę.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

w powyższym przykładzie X i Y mają takie same wartości, tj. 1, a gdy użyjemy ==, zwróci true, tak jak w przypadku ==, krótki typ jest konwertowany na int przez kompilator i wynik jest podawany.

a kiedy używamy Equals, porównywanie jest zakończone, ale rzutowanie typu nie jest wykonywane przez kompilator, więc zwracane jest false.

Chłopaki, dajcie znać, jeśli się mylę.

użytkownik2423959
źródło
6

W wielu kontekstach, w których argument metody lub operatora nie jest wymaganego typu, kompilator C # będzie próbował przeprowadzić niejawną konwersję typu. Jeśli kompilator może sprawić, że wszystkie argumenty spełniają ich operatory i metody poprzez dodanie niejawnych konwersji, zrobi to bez zarzutu, nawet jeśli w niektórych przypadkach (szczególnie z testami równości!) Wyniki mogą być zaskakujące.

Ponadto, każdy typ wartości taki jak intlub shortfaktycznie opisuje zarówno rodzaj wartości, jak i rodzaj obiektu (*). Istnieją niejawne konwersje w celu konwersji wartości na inne rodzaje wartości oraz w celu konwersji dowolnego rodzaju wartości na odpowiadający mu rodzaj obiektu, ale różne rodzaje obiektów nie są wzajemnie konwertowalne.

Jeśli użyje się ==operatora do porównania a shorti an int, shortzostanie ono domyślnie przekonwertowane na an int. Jeśli jego wartość liczbowa była równa wartości, wartość int, na intktórą został przeliczony, będzie równa wartości, intz którą jest porównywany. Jeśli jednak spróbujemy użyć Equalsmetody w skrócie, aby porównać ją z intjedyną niejawną konwersją, która zaspokoi przeciążenie Equalsmetody, będzie konwersja na typ obiektu odpowiadający int. Kiedy shortzapyta się, czy pasuje do przekazanego obiektu, zauważy, że przedmiotowy przedmiot jest intraczej znakiem a niż shorta, co oznacza, że ​​nie może być równy.

Ogólnie rzecz biorąc, chociaż kompilator nie będzie na to narzekać, należy unikać porównywania rzeczy, które nie są tego samego typu; jeśli ktoś jest zainteresowany tym, czy konwersja rzeczy do wspólnej formy dałaby ten sam wynik, należy taką konwersję wykonać jawnie. Zastanów się na przykład

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

Istnieją trzy sposoby porównywania intz float. Można chcieć wiedzieć:

  1. Czy najbliższa możliwa floatwartość intodpowiada wartości float?
  2. Czy część z liczbą całkowitą floatpasuje do int?
  3. Wykonaj inti floatreprezentuj tę samą wartość liczbową.

Jeśli ktoś próbuje porównać inti floatbezpośrednio, skompilowany kod będzie odpowiedzieć na pierwsze pytanie; czy tego właśnie zamierzał programista, nie będzie to jednak oczywiste. Zmiana porównania na (float)i == fwyjaśni, że pierwsze znaczenie było zamierzone, lub (double)i == (double)fspowoduje, że kod odpowie na trzecie pytanie (i wyjaśni, że to było zamierzone).

(*) Nawet jeśli specyfikacja C # traktuje wartość typu np. System.Int32Jako obiekt typu System.Int32, temu poglądowi zaprzecza wymóg, aby kod działał na platformie, której specyfikacja traktuje wartości i obiekty jako zamieszkujące różne wszechświaty. Ponadto, jeśli Tjest typem odniesienia, a xjest to T, to odniesienie typu Tpowinno być w stanie się odnosić x. Zatem, jeśli zmienna vtypu Int32zawiera an Object, odwołanie do typu Objectpowinno być w stanie pomieścić odwołanie do vlub jego zawartość. W rzeczywistości odniesienie typu Objectbyłoby w stanie wskazać obiekt zawierający dane skopiowane v, ale nie do vsiebie ani do jego zawartości. To by sugerowało, że żadne z nichvani jego zawartość nie jest tak naprawdę Object.

supercat
źródło
1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intŹle. W przeciwieństwie do Java, C # nie ma oddzielnych typów pierwotnych i pudełkowych. Jest zapakowane, objectponieważ jest to jedyne inne przeciążenie Equals().
SLaks
Pierwsze i trzecie pytanie są identyczne; dokładna wartość została już utracona podczas konwersji na float. Rzut floatna a doublenie magicznie stworzy nową precyzję.
SLaks
@SLaks: Zgodnie ze specyfikacją ECMA, która opisuje maszynę wirtualną, na której działa C #, każda definicja typu wartości tworzy dwa różne typy. Specyfikacja C # może powiedzieć, że zawartość miejsca przechowywania typu List<String>.Enumeratori obiektu sterty typu List<String>.Enumeratorsą takie same, ale specyfikacja ECMA / CLI mówi, że są różne, a nawet gdy są używane w C #, zachowują się inaczej.
supercat
@SLaks: Jeśli ii fzostałyby przekonwertowane doubleprzed porównaniem, otrzymałyby 16777217.0 i 16777216.0, które są porównywane jako nierówne. Konwersja i floatdałoby 16777216.0f, w porównaniu równa się f.
supercat
@SLaks: Aby zapoznać się z prostym przykładem różnicy między typami lokalizacji do przechowywania a typami obiektów w pudełkach, rozważ metodę bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Typ obiektu w ramce odpowiadający typowi wartości może spełniać typ parametru ReferenceEqualspoprzez upcast zachowujący tożsamość ; typ lokalizacji magazynu wymaga jednak konwersji nieobsługującej tożsamości . Jeśli odlewania Tdo Urentowności odniesienie do czegoś innego niż oryginał T, który sugerował mi się, że Ttak naprawdę nie jest U.
supercat
5

Equals () jest metodą System.Object klasy
składni: wirtualne równi public bool ()
Zalecenie jeśli chcemy porównać stan dwóch obiektów wówczas powinniśmy użyć equals () metodę

jak podano powyżej odpowiedzi == operatorzy porównują wartości są takie same.

Proszę się nie mylić z ReferenceEqual

Reference Equals ()
Składnia: public static bool ReferenceEquals ()
Określa, czy określona instancja obiektów jest tej samej instancji

Sugat Mankar
źródło
8
To wcale nie odpowiada na pytanie.
SLaks
SLaks nie wyjaśniłem przykładami, są to podstawowe pytania powyższego pytania.
Sugat Mankar
4

Musisz zdać sobie sprawę z tego, że działanie ==zawsze kończy się wywołaniem metody. Pytanie brzmi, czy dzwonienie ==i Equalskończy dzwonienie / robienie tych samych rzeczy.

W przypadku typów referencyjnych ==zawsze 1. sprawdza, czy referencje są takie same ( Object.ReferenceEquals). Equalsz drugiej strony może zostać zastąpione i może sprawdzić, czy niektóre wartości są równe.

EDYCJA: aby odpowiedzieć svick i dodać komentarz SLaks, oto trochę kodu IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.
użytkownik276648
źródło
Więc jaką metodę porównuje dwa ints z wywołaniem ==? Wskazówka: nie ma operator ==metody dla Int32, ale jest jedna dlaString .
svick
2
To w ogóle nie odpowiada na pytanie.
SLaks
@SLaks: to naprawdę nie odpowiada na konkretne pytanie dotyczące int i krótkiego porównania, już odpowiedziałeś. Nadal uważam za interesujące wyjaśnienie, że ==nie tylko robi magię, ale w końcu po prostu wywołuje metodę (większość programistów prawdopodobnie nigdy nie wdrożyła / nie zastąpiła żadnego operatora). Może mógłbym dodać komentarz do twojego pytania zamiast własnej odpowiedzi. Zaktualizuj swój, jeśli uważasz, że to, co powiedziałem, jest istotne.
user276648,
Zauważ, że ==w typach pierwotnych nie jest przeciążonym operatorem, ale właściwością języka wewnętrznego, która kompiluje się z ceqinstrukcją IL.
SLaks
3

== W prymitywnych

Console.WriteLine(age == newAge);          // true

W pierwotnym porównaniu operator == zachowuje się dość oczywisto, w języku C # dostępnych jest wiele przeciążeń operatora ==.

  • ciąg == ciąg
  • int == int
  • uint == uint
  • długi == długi
  • wiele więcej

Zatem w tym przypadku nie jest możliwa domyślna konwersja z intna, shortale shortna intmożliwa. Więc newAge jest konwertowany na int i następuje porównanie, które zwraca true, ponieważ oba mają tę samą wartość. Jest to więc równoważne z:

Console.WriteLine(age == (int)newAge);          // true

.Equals () w Primitive

Console.WriteLine(newAge.Equals(age));         //false

Tutaj musimy zobaczyć, co to jest metoda Equals (), nazywamy Equals zmienną krótkiego typu. Istnieją więc trzy możliwości:

  • Równa się (obiekt, obiekt) // metoda statyczna z obiektu
  • Równa się (obiekt) // metoda wirtualna z obiektu
  • Równa się (krótka) // Implementuje IEquatable.Equals (krótka)

Pierwszy typ nie ma tu znaczenia, ponieważ liczba argumentów jest inna, wywołujemy tylko jednym argumentem typu int. Trzeci również został wyeliminowany, jak wspomniano powyżej, nie jest możliwa domniemana konwersja int na short. Więc tutaj Equals(object)nazywany jest drugi typ . Jest short.Equals(object)to:

bool Equals(object z)
{
  return z is short && (short)z == this;
}

Więc tutaj sprawdzono warunek, z is shortktóry jest fałszywy, ponieważ z jest int, więc zwraca false.

Oto szczegółowy artykuł Erica Lipperta

Zaheer Ahmed
źródło