Mengapa (object) 0 == (object) 0 berbeda dari ((object) 0) .Equals ((object) 0)?

117

Mengapa ekspresi berikut berbeda?

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

Sebenarnya, saya benar-benar dapat memahami [1] karena mungkin runtime NET. Akan boxinteger dan mulai membandingkan referensi sebagai gantinya. Tetapi mengapa [2] berbeda?

André Pena
sumber
36
Oke, sekarang Anda sudah memahami jawaban atas pertanyaan ini, periksa pemahaman Anda dengan memprediksi hasil dari: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Sekarang periksa dengan kenyataan. Apakah prediksi Anda benar? Jika tidak, dapatkah Anda menjelaskan perbedaannya?
Eric Lippert
1
@Star, untuk bacaan yang disarankan, lihat msdn.microsoft.com/en-us/library/vstudio/… untuk kelebihan beban yang tersedia pada metode int16aka shortEquals, lalu lihat msdn.microsoft.com/en-us/library/ms173105.aspx . Saya tidak ingin merusak teka-teki Eric Lippert, tetapi seharusnya cukup mudah untuk mengetahuinya setelah Anda membaca halaman-halaman itu.
Sam Skuce
2
Saya pikir ini adalah pertanyaan java; setidaknya sebelum melihat 'E' di Equals.
seteropere
4
@seteropere Java sebenarnya berbeda: autoboxing di Java menyimpan cache objek, jadi ((Integer)0)==((Integer)0)mengevaluasi ke true.
Jules
1
Anda juga bisa mencoba IFormattable x = 0; bool test = (object)x == (object)x;. Tidak ada tinju baru yang dilakukan saat struct sudah ada di dalam kotak.
Jeppe Stig Nielsen

Jawaban:

151

Alasan panggilan berperilaku berbeda adalah karena panggilan tersebut terikat ke metode yang sangat berbeda.

The ==kasus akan mengikat operator kesetaraan referensi statis. Ada 2 kotak independenint nilai dibuat karena itu bukan referensi yang sama.

Dalam kasus kedua, Anda mengikat ke metode instance Object.Equals. Ini adalah metode virtual yang akan memfilter Int32.Equalsdan ini memeriksa bilangan bulat dalam kotak. Kedua nilai integer adalah 0 sehingga keduanya sama

JaredPar
sumber
The ==kasus tidak menelepon Object.ReferenceEquals. Ini hanya menghasilkan ceqinstruksi IL untuk melakukan perbandingan referensi.
Sam Harwell
8
@ 280Z28 bukankah itu hanya karena kompilator menyebariskannya?
markmnl
@ 28028 Jadi? Kasus serupa adalah metode Boolean.ToString mereka tampaknya berisi string yang di-hardcode di dalam fungsinya, alih-alih menampilkan Boolean.TrueString dan Boolean.FalseString yang diekspos secara publik. Itu tidak relevan; Intinya adalah, ==lakukan hal yang sama seperti ReferenceEquals(pada Object). Itu semua hanya pengoptimalan internal di sisi MS untuk menghindari panggilan fungsi internal yang tidak perlu pada fungsi yang sering digunakan.
Nyerguds
6
Spesifikasi Bahasa C #, paragraf 7.10.6, mengatakan: Operator persamaan jenis referensi yang telah ditentukan sebelumnya adalah: bool operator ==(object x, object y); bool operator !=(object x, object y);Operator mengembalikan hasil dari membandingkan dua referensi untuk persamaan atau non-persamaan. Metode tersebut tidak diwajibkan System.Object.ReferenceEqualsuntuk menentukan hasil. Kepada @markmnl: Tidak, kompilator C # tidak sebaris, itu adalah sesuatu yang kadang-kadang dilakukan oleh jitter (tetapi tidak dalam kasus ini). Jadi 280Z28 benar ReferenceEqualsmetode ini sebenarnya tidak digunakan.
Jeppe Stig Nielsen
@JaredPar: Sangat menarik bahwa spesifikasi mengatakan itu, karena bahasa sebenarnya tidak berperilaku seperti itu. Mengingat operator didefinisikan seperti di atas, dan variabel Cat Whiskers; Dog Fido; IDog Fred;(untuk antarmuka non-terkait ICatdan IDogdan kelas non-terkait Cat:ICatdan Dog:IDog), perbandingan Whiskers==Fidodan Whiskers==34akan menjadi hukum (yang pertama hanya bisa menjadi benar jika Kumis dan Fido berdua null; yang kedua tidak pernah bisa benar ). Nyatanya, kompilator C # akan menolak keduanya. Whiskers==Fred;akan dilarang jika Catdisegel, tetapi diizinkan jika tidak.
supercat
26

Saat Anda memasukkan nilai int 0(atau tipe nilai lainnya) ke object, nilainya dikotakkan . Setiap cast objectmenghasilkan kotak yang berbeda (contoh objek yang berbeda). The ==Operator untukobject jenis melakukan referensi perbandingan, sehingga ia mengembalikan palsu karena sisi kiri dan sisi kanan tidak contoh yang sama.

Di sisi lain, saat Anda menggunakan Equals, yang merupakan metode virtual, ia menggunakan implementasi tipe kotak yang sebenarnya, yaitu Int32.Equals, yang mengembalikan nilai true karena kedua objek memiliki nilai yang sama.

Thomas Levesque
sumber
18

The ==operator, menjadi statis, tidak virtual. Ini akan menjalankan kode persis seperti ituobject didefinisikan oleh kelas (`objek menjadi jenis waktu kompilasi dari operan), yang akan melakukan perbandingan referensi, terlepas dari jenis waktu proses dari salah satu objek.

The Equalsmetode adalah metode contoh virtual. Ini akan menjalankan kode yang ditentukan dalam jenis run-time sebenarnya dari objek (pertama), bukan kode di objectkelas. Dalam hal ini, objeknya adalah an int, sehingga akan melakukan perbandingan nilai, karena itulah yang intdidefinisikan oleh tipe untuk Equalsmetodenya.

Pelayanan
sumber
The ==Token sebenarnya merupakan dua operator, salah satunya adalah overloadable dan salah satu yang tidak. Perilaku operator kedua sangat berbeda dengan perilaku overload (objek, objek).
supercat
13

The Equals()metode adalah virtual.
Oleh karena itu, itu selalu memanggil implementasi konkret, bahkan ketika callsite dicor object. intmenimpa Equals()untuk membandingkan berdasarkan nilai, sehingga Anda mendapatkan perbandingan nilai.

SLaks
sumber
10

== Menggunakan: Object.ReferenceEquals

Object.Equals membandingkan nilainya.

Itu object.ReferenceEquals metode membandingkan referensi. Saat mengalokasikan objek, Anda menerima referensi berisi nilai yang menunjukkan lokasi memorinya selain data objek di heap memori.

The object.Equalsmetode membandingkan isi objek. Ini pertama kali memeriksa apakah referensinya sama, seperti halnya object.ReferenceEquals. Tapi kemudian memanggil ke dalam metode Equals turunan untuk menguji kesetaraan lebih lanjut. Lihat ini:

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

sumber
Meskipun Object.ReferenceEqualsberperilaku seperti metode yang menggunakan ==operator C # pada operannya, operator operator persamaan referensi C # (yang direpresentasikan dengan menggunakan ==jenis operan yang tidak memiliki kelebihan beban yang ditentukan) menggunakan instruksi khusus daripada memanggil ReferenceEquals. Selanjutnya, Object.ReferenceEqualsakan menerima operan yang hanya bisa cocok jika keduanya kebetulan nol, dan akan menerima operan yang perlu dipaksakan tipe Objectdan dengan demikian tidak mungkin cocok dengan apa pun, sementara versi referensi-kesetaraan ==akan menolak untuk mengkompilasi penggunaan tersebut .
supercat
9

Operator C # menggunakan token ==untuk mewakili dua operator yang berbeda: operator perbandingan yang dapat kelebihan beban secara statis dan operator perbandingan referensi yang tidak dapat dilebihkan. Saat menemukan ==token, pertama kali memeriksa untuk melihat apakah ada kelebihan pengujian kesetaraan yang berlaku untuk jenis operan. Jika demikian, itu akan memicu kelebihan itu. Jika tidak, ini akan memeriksa apakah tipe tersebut berlaku untuk operator perbandingan referensi. Jika demikian, itu akan menggunakan operator itu. Jika tidak ada operator yang dapat diterapkan ke jenis operan, kompilasi akan gagal.

Kode (Object)0tersebut tidak hanya memuat Int32keObject : Int32, seperti semua jenis nilai, benar-benar mewakili dua jenis, salah satu yang menggambarkan nilai-nilai dan lokasi penyimpanan (seperti nol literal), tetapi tidak berasal dari apa-apa, dan salah satu yang menggambarkan benda tumpukan dan berasal dari Object; karena hanya tipe terakhir yang mungkin upcast Object, compiler harus membuat objek heap baru dari tipe yang terakhir tersebut. Setiap pemanggilan (Object)0membuat objek heap baru, sehingga dua operan ==adalah objek yang berbeda, yang masing-masing, secara independen, merangkum Int32nilai 0.

Kelas Objecttidak memiliki kelebihan beban yang dapat digunakan yang ditentukan untuk operator yang sama. Akibatnya, kompilator tidak akan dapat menggunakan operator uji persamaan yang kelebihan beban, dan akan kembali menggunakan uji persamaan referensi. Karena dua operan ==merujuk ke objek yang berbeda, itu akan melaporkan false. Perbandingan kedua berhasil karena menanyakan satu instance objek heap Int32apakah sama dengan yang lain. Karena contoh itu tahu apa artinya sama dengan contoh berbeda lainnya, dia bisa menjawab true.

supercat
sumber
Selain itu, setiap kali Anda menulis literal 0dalam kode Anda, saya berasumsi itu membuat objek int di heap untuk itu. Ini bukan referensi unik untuk satu nilai nol statis global (seperti bagaimana mereka membuat String.Empty untuk menghindari membuat objek string kosong baru hanya untuk menginisialisasi string baru) Jadi saya cukup yakin bahwa bahkan melakukan a 0.ReferenceEquals(0)akan mengembalikan false, karena kedua 0 adalah objek yang baru dibuat Int32.
Nyerguds
1
@Nyerguds, saya cukup yakin semua yang Anda katakan salah, tentang ints, heap, histori, statika global, dll. 0.ReferenceEquals(0)Akan gagal karena Anda mencoba memanggil metode pada konstanta waktu kompilasi. tidak ada objek untuk menggantungnya. Int unboxed adalah struct, disimpan di stack. Bahkan int i = 0; i.ReferenceEquals(...)tidak akan berhasil. Karena System.Int32TIDAK mewarisi dari Object.
Andrew Backer
@AndrewBacker, System.Int32adalah struct, structadalah System.ValueType, yang mewarisi dirinya sendiri System.Object. Itulah mengapa ada ToString()metode dan Equalsmetode untukSystem.Int32
Sebastian
1
Namun demikian, Nyerguds salah untuk menyatakan bahwa Int32 akan dibuat di heap, padahal sebenarnya tidak demikian.
Sebastian
@SebastianGodelet, saya agak mengabaikan bagian dalam itu. System.Int32 sendiri mengimplementasikan metode tersebut. GetType () ada di luar Object, dan di sanalah saya berhenti mengkhawatirkannya sejak lama. Tidak perlu melangkah lebih jauh. AFAIK CLR menangani dua jenis secara berbeda, dan secara khusus. Bukan hanya warisan. Ini issalah satu dari dua jenis data. Saya hanya tidak ingin ada orang yang membaca komentar itu dan keluar jalur, termasuk keanehan tentang string kosong yang mengabaikan proses internal string.
Andrew Backer
3

Kedua pemeriksaan tersebut berbeda. Yang pertama memeriksa identitas , yang kedua untuk kesetaraan . Secara umum, dua istilah identik, jika merujuk pada objek yang sama. Ini menyiratkan bahwa mereka setara. Dua suku sama, jika nilainya sama.

Dalam istilah identitas pemrograman biasanya dirusak oleh persamaan referensi. Jika penunjuk ke kedua suku sama (!), Maka obyek yang mereka tunjuk sama persis. Namun, jika penunjuknya berbeda, nilai objek yang dituju masih bisa sama. Dalam C # identitas dapat diperiksa dengan menggunakan Object.ReferenceEqualsanggota statis , sedangkan kesetaraan diperiksa menggunakan anggota non-statis Object.Equals. Karena Anda pengecoran dua bilangan bulat ke objek (yang disebut "tinju", btw), yang operatior ==dari objectmelakukan cek pertama, yang secara default dipetakan ke Object.ReferenceEqualsdan memeriksa identitas. Jika Anda secara eksplisit memanggil Equals-member non-statis , pengiriman dinamis menghasilkan panggilan ke Int32.Equals, yang memeriksa kesetaraan.

Kedua konsep tersebut serupa, tetapi tidak sama. Awalnya mungkin tampak membingungkan, tetapi perbedaan kecil itu sangat penting! Bayangkan dua orang, yaitu "Alice" dan "Bob". Mereka berdua tinggal di rumah kuning. Berdasarkan asumsi, Alice dan Bob tinggal di sebuah distrik yang rumahnya hanya berbeda warna, keduanya bisa tinggal di rumah kuning yang berbeda. Jika Anda membandingkan kedua rumah, Anda akan mengenali, bahwa keduanya sama sekali, karena keduanya berwarna kuning! Namun, mereka tidak berbagi rumah yang sama sehingga rumah mereka sama , tetapi tidak identik . Identitas menyiratkan bahwa mereka tinggal di rumah yang sama .

Catatan : beberapa bahasa menentukan ===operator untuk memeriksa identitas.

Carsten
sumber