Apa perbedaan antara == dan Equals () untuk primitif dalam C #?


180

Pertimbangkan kode ini:

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

Keduanya intdan shortmerupakan tipe primitif, tetapi perbandingan dengan ==pengembalian benar dan perbandingan dengan Equalspengembalian salah.

Mengapa?


9
@OrangeDog Silakan Pikirkan tentang pertanyaan dan kemudian pilih untuk menutup

4
Ini tidak ada pada upaya sebaliknya yang jelas:Console.WriteLine(age.Equals(newAge));
ANeves

3
Duplikat tidak menjelaskan perilaku ini; ini hanya tentang apa Equals()yang secara umum.
SLaks

37
Saya menjawab pertanyaan yang tepat ini di blog Coverity beberapa hari yang lalu. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert

5
@CodesInChaos: Spesifikasi sebenarnya menggunakan istilah "tipe primitif" dua kali tanpa pernah mendefinisikannya; implikasinya adalah bahwa tipe primitif adalah tipe nilai bawaan, tetapi ini tidak pernah dibuat jelas. Saya telah merekomendasikan kepada Mads bahwa istilah itu hanya dicabut dari spesifikasi karena tampaknya membuat lebih banyak kebingungan daripada menghilangkan.
Eric Lippert

Jawaban:


262

Jawaban singkat:

Kesetaraan itu rumit.

Jawaban terinci:

Tipe primitif menimpa basis object.Equals(object)dan mengembalikan true jika kotak objectitu dari jenis dan nilai yang sama. (Perhatikan bahwa ini juga akan berfungsi untuk tipe yang dapat dibatalkan; tipe yang tidak dapat dibatalkan dapat selalu kotak ke instance dari tipe yang mendasarinya.)

Karena newAgea short, Equals(object)metodenya hanya mengembalikan true jika Anda melewatkan kotak pendek dengan nilai yang sama. Anda melewati kotak int, jadi mengembalikan salah.

Sebaliknya, ==operator didefinisikan sebagai mengambil dua ints (atau shorts atau longs).
Ketika Anda memanggilnya dengan a intdan a short, kompiler akan secara implisit mengonversikan shortke intdan membandingkan hasil intdengan nilai.

Cara lain untuk membuatnya bekerja

Tipe primitif juga memiliki Equals()metode mereka sendiri yang menerima tipe yang sama.
Jika Anda menulis age.Equals(newAge), kompiler akan memilih int.Equals(int)sebagai kelebihan beban terbaik dan secara implisit dikonversi shortke int. Kemudian akan kembali true, karena metode ini hanya membandingkan ints secara langsung.

shortjuga memiliki short.Equals(short)metode, tetapi inttidak dapat secara implisit dikonversi short, jadi Anda tidak memanggilnya.

Anda bisa memaksanya untuk memanggil metode ini dengan gips:

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

Ini akan menelepon short.Equals(short)langsung, tanpa tinju. Jika agelebih besar dari 32767, itu akan membuang pengecualian melimpah.

Anda juga bisa memanggil short.Equals(object)kelebihan, tetapi secara eksplisit melewati objek kotak sehingga mendapat jenis yang sama:

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

Seperti alternatif sebelumnya, ini akan membuang kelebihan jika tidak sesuai short. Berbeda dengan solusi sebelumnya, ia akan mengotakkan shortobjek, membuang-buang waktu dan memori.

Kode sumber:

Berikut adalah kedua Equals()metode dari kode sumber aktual:

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

Bacaan lebih lanjut:

Lihat Eric Lippert .


3
@ SLaks, jika kita memanggil long == int, intsecara implisit dikonversi ke longkanan?
Selman Genç

1
Dan ya, saya menulis semua itu tanpa benar-benar mencobanya.
SLaks

1
Ingat bahwa, dalam kode dari pertanyaan, jika salah satu perubahan int age = 25;untuk const int age = 25;, maka hasilnya akan berubah. Itu karena konversi implisit dari intke shortmemang ada dalam kasus itu. Lihat Konversi ekspresi konstan implisit .
Jeppe Stig Nielsen

2
@ SLaks ya tetapi kata-kata dari jawaban Anda "nilai yang diteruskan" dapat diartikan sebagai dua cara (sebagai nilai yang diteruskan oleh pengembang, atau nilai yang sebenarnya diteruskan oleh CLR setelah unboxing). Saya menduga pengguna biasa yang belum tahu jawabannya di sini akan membacanya sebagai mantan
JaredPar

2
@ Rachel: Kecuali itu tidak benar; yang standar == Operator membandingkan jenis referensi dengan referensi. Untuk tipe nilai, dan untuk tipe yang kelebihan beban ==, tidak.
SLaks

55

Karena tidak ada kelebihan untuk short.Equalsyang menerima int. Oleh karena itu, ini disebut:

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

objbukan short.. karena itu, itu salah.


12

Ketika Anda lulus intke shortSama Anda lulus object:

masukkan deskripsi gambar di sini Jadi pseudocode ini berjalan:

return obj is short && this == (short)obj;


10

==digunakan untuk memeriksa kondisi yang sama, dapat dianggap sebagai operator (operator boolean), hanya untuk membandingkan 2 hal dan di sini tipe data tidak masalah karena akan ada tipe casting yang dilakukan dan Equalsjuga digunakan untuk memeriksa kondisi yang sama , tetapi dalam hal ini tipe data harus sama. N Equals adalah metode bukan operator.

Di bawah ini adalah contoh kecil yang diambil dari yang Anda berikan dan ini akan menjelaskan perbedaan secara singkat ,.

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

dalam contoh di atas, X dan Y memiliki nilai yang sama yaitu 1, dan ketika kita menggunakan ==, itu akan mengembalikan true, seperti dalam kasus ==, tipe pendek dikonversi menjadi int oleh kompiler dan hasilnya diberikan.

dan ketika kita gunakan Equals, pembandingannya dilakukan, tetapi pengecoran tipe tidak dilakukan oleh kompiler, jadi false dikembalikan.

Kawan, tolong beri tahu saya jika saya salah.


6

Dalam banyak konteks di mana metode atau argumen operator bukan dari tipe yang diperlukan, kompiler C # akan berusaha untuk melakukan konversi tipe implisit. Jika kompiler dapat membuat semua argumen memuaskan operator dan metode mereka dengan menambahkan konversi implisit, ia akan melakukannya tanpa keluhan, meskipun dalam beberapa kasus (terutama dengan tes kesetaraan!) Hasilnya mungkin mengejutkan.

Selanjutnya, setiap tipe nilai seperti intatau shortbenar - benar menggambarkan baik jenis nilai maupun jenis objek (*). Konversi tersirat ada untuk mengkonversi nilai ke jenis nilai lainnya, dan untuk mengkonversi segala jenis nilai ke jenis objek yang sesuai, tetapi berbagai jenis objek tidak secara implisit dapat dikonversi satu sama lain.

Jika seseorang menggunakan ==operator untuk membandingkan a shortdan a int, maka shortakan secara implisit dikonversi menjadi int. Jika nilai numeriknya sama dengan nilai int, intyang dikonversi akan sama intdengan yang dibandingkan. Jika seseorang mencoba menggunakan Equalsmetode ini untuk membandingkannya dengan int, bagaimanapun, satu-satunya konversi implisit yang akan memenuhi kelebihan Equalsmetode adalah konversi ke jenis objek yang sesuai dengan int. Ketika shortditanya apakah cocok dengan objek yang lewat, ia akan mengamati bahwa objek tersebut adalah intbukan daripada shortdan dengan demikian menyimpulkan bahwa itu tidak mungkin sama.

Secara umum, meskipun kompiler tidak akan mengeluh tentang hal itu, orang harus menghindari membandingkan hal-hal yang tidak dari jenis yang sama; jika seseorang tertarik pada apakah konversi sesuatu ke bentuk umum akan memberikan hasil yang sama, ia harus melakukan konversi seperti itu secara eksplisit. Pertimbangkan, misalnya,

int i = 16777217;
float f = 16777216.0f;

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

Ada tiga cara di mana seseorang mungkin ingin membandingkan intdengan float. Orang mungkin ingin tahu:

  1. Apakah nilai terdekat floatyang intcocok dengan cocok float?
  2. Apakah bilangan bulat bagian dari floatpertandingan cocok int?
  3. Lakukan intdan floatmewakili nilai numerik yang sama.

Jika seseorang mencoba membandingkan intdan floatsecara langsung, kode yang dikompilasi akan menjawab pertanyaan pertama; apakah itu yang dimaksudkan oleh programmer, akan jauh dari jelas. Mengubah perbandingan untuk (float)i == fmemperjelas bahwa makna pertama dimaksudkan, atau (double)i == (double)fakan menyebabkan kode menjawab pertanyaan ketiga (dan membuatnya jelas itulah yang dimaksudkan).

(*) Sekalipun spec C # menganggap nilai tipe misalnya System.Int32sebagai objek tipe System.Int32, pandangan seperti itu bertentangan dengan persyaratan bahwa kode dijalankan pada platform yang specnya menganggap nilai dan objek sebagai penghuni alam semesta yang berbeda. Selanjutnya, jika Tmerupakan tipe referensi, dan xadalah T, maka referensi tipe Tharus dapat merujuk x. Jadi, jika suatu variabel vbertipe Int32memegang Object, suatu referensi bertipe Objectharus dapat menyimpan referensi vatau isinya. Bahkan, referensi tipe Objectakan dapat menunjuk ke objek yang menyimpan data yang disalin v, tetapi tidak untuk vdirinya sendiri atau kontennya. Itu tidak menyarankan keduanyavatau isinya benar-benar sebuah Object.


1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intSalah. Tidak seperti Java, C # tidak memiliki tipe primitif dan kotak yang terpisah. Ini sedang dikotak objectkarena itu satu-satunya kelebihan lainnya Equals().
SLaks

Pertanyaan pertama dan ketiga identik; nilai pastinya telah hilang saat konversi ke float. Casting a floatto a doubletidak akan secara ajaib menciptakan presisi baru.
SLaks

@SLaks: Menurut spesifikasi ECMA, yang menggambarkan mesin virtual tempat C # dijalankan, setiap definisi tipe nilai membuat dua tipe berbeda. Spesifikasi C # dapat mengatakan bahwa isi dari lokasi penyimpanan tipe List<String>.Enumeratordan objek bertipe heap List<String>.Enumeratoradalah sama, tetapi spesifikasi ECMA / CLI mengatakan mereka berbeda, dan bahkan ketika digunakan dalam C # mereka berperilaku berbeda.
supercat

@ SLaks: Jika idan fmasing-masing dikonversi ke doublesebelum perbandingan, mereka akan menghasilkan 16777217.0 dan 16777216.0, yang dibandingkan sebagai tidak setara. Konversi i floatakan menghasilkan 16777216.0f, dibandingkan dengan f.
supercat

@SLaks: Untuk contoh sederhana dari perbedaan antara tipe lokasi penyimpanan dan tipe objek kotak, pertimbangkan metode ini bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Jenis objek kotak yang sesuai dengan tipe nilai dapat memenuhi tipe parameter ReferenceEqualsmelalui identitas yang mempertahankan upcast; tipe lokasi penyimpanan, bagaimanapun, membutuhkan konversi yang tidak melindungi identitas . Jika melemparkan Tke Umenghasilkan referensi ke sesuatu selain yang asli T, itu akan menyarankan kepada saya bahwa Ttidak benar-benar a U.
supercat

5

Equals () adalah metode System.Object Class
Syntax: Public virtual bool Equals ()
Rekomendasi jika kita ingin membandingkan keadaan dua objek maka kita harus menggunakan metode Equals ()

sebagaimana dinyatakan di atas jawaban == operator membandingkan nilainya sama.

Tolong jangan bingung dengan ReferenceEqual

Reference Equals ()
Sintaks: public static bool ReferenceEquals ()
Ini menentukan apakah instance objek yang ditentukan memiliki instance yang sama


8
Ini sama sekali tidak menjawab pertanyaan.
SLaks

Slaks i dnt dijelaskan dengan contoh-contoh ini adalah dasar dari pertanyaan di atas.
Sugat Mankar

4

Yang perlu Anda sadari adalah bahwa melakukan ==akan selalu berakhir dengan memanggil metode. Pertanyaannya adalah apakah memanggil ==dan Equalsakhirnya memanggil / melakukan hal yang sama.

Dengan tipe referensi, ==akan selalu memeriksa 1 apakah referensi sama ( Object.ReferenceEquals). Equalsdi sisi lain dapat diganti dan dapat memeriksa apakah beberapa nilai sama.

EDIT: untuk menjawab svick dan menambahkan komentar SLaks, berikut adalah beberapa kode 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.

Jadi metode apa yang membandingkan dua ints dengan panggilan ==? Petunjuk: tidak ada operator ==metode untuk Int32, tetapi ada satu untukString .
svick

2
Ini sama sekali tidak menjawab pertanyaan.
SLaks

@ SLaks: memang tidak menjawab pertanyaan spesifik tentang int dan perbandingan singkat, Anda sudah menjawabnya. Saya masih merasa menarik untuk menjelaskan bahwa ==tidak hanya melakukan sihir, pada akhirnya hanya memanggil metode (kebanyakan programmer mungkin tidak pernah mengimplementasikan / menimpa operator apa pun). Mungkin saya bisa menambahkan komentar ke pertanyaan Anda alih-alih menambahkan jawaban saya sendiri. Jangan ragu untuk memperbarui milik Anda jika Anda merasa apa yang saya katakan relevan.
user276648

Perhatikan bahwa ==pada tipe primitif bukan operator kelebihan beban, tetapi fitur bahasa intrinsik yang mengkompilasi ceqinstruksi IL.
SLaks

3

== Dalam Primitif

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

Dalam perbandingan primitif == operator berperilaku cukup jelas, Di C # ada banyak == operator kelebihan tersedia.

  • string == string
  • int == int
  • uint == uint
  • long == long
  • masih banyak lagi

Jadi dalam hal ini tidak ada konversi tersirat dari intke shorttetapi shortmenjadi intmungkin. Jadi newAge dikonversi menjadi int dan terjadi perbandingan yang mengembalikan nilai true karena keduanya memiliki nilai yang sama. Jadi itu setara dengan:

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

.Equals () dalam Primitive

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

Di sini kita perlu melihat apa metode Equals (), kita memanggil Equals dengan variabel tipe pendek. Jadi ada tiga kemungkinan:

  • Sama dengan (objek, objek) // metode statis dari objek
  • Sama dengan (objek) // metode virtual dari objek
  • Sama dengan (pendek) // Menerapkan IEquatable.Equals (pendek)

Jenis pertama tidak berlaku di sini karena jumlah argumen berbeda yang kami panggil dengan hanya satu argumen bertipe int. Ketiga juga dihilangkan seperti yang disebutkan di atas konversi int ke pendek tidak mungkin. Jadi di sini tipe kedua Equals(object)disebut. The short.Equals(object)adalah:

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

Jadi di sini kondisi diuji z is shortyang salah karena z adalah int sehingga mengembalikan salah.

Ini adalah artikel terperinci dari Eric Lippert

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.