Bagaimana cara memeriksa null di operator '==' yang kelebihan beban tanpa rekursi tak terbatas?

113

Berikut ini akan menyebabkan rekursi tak terbatas pada == metode kelebihan beban operator

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

Bagaimana cara memeriksa nulls?

Andrew Jones
sumber

Jawaban:

138

Penggunaan ReferenceEquals:

Foo foo1 = null;
Foo foo2 = new Foo();
Assert.IsFalse(foo1 == foo2);

public static bool operator ==(Foo foo1, Foo foo2) {
    if (object.ReferenceEquals(null, foo1))
        return object.ReferenceEquals(null, foo2);
    return foo1.Equals(foo2);
}
Abe Heidebrecht
sumber
Solusi ini tidak berfungsi untukAssert.IsFalse(foo2 == foo1);
FIL
Dan apa foo1.Equals(foo2)artinya jika, misalnya, saya ingin foo1 == foo2hanya jika foo1.x == foo2.x && foo1.y == foo2.y? Bukankah ini menjawab mengabaikan kasus dimana foo1 != nulltapi foo2 == null?
Daniel
Catatan: Solusi yang sama dengan sintaks yang lebih sederhana:if (foo1 is null) return foo2 is null;
Rem
20

Transmisikan ke objek dalam metode kelebihan beban:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}
Andrew Jones
sumber
1
Persis. Keduanya (object)foo1 == nullatau foo1 == (object)nullakan pergi ke kelebihan beban bawaan ==(object, object)dan bukan ke beban berlebih yang ditentukan pengguna ==(Foo, Foo). Ini seperti resolusi yang berlebihan pada metode.
Jeppe Stig Nielsen
2
Untuk pengunjung masa depan - jawaban yang diterima adalah fungsi, yang mengeksekusi == objek. Ini pada dasarnya sama dengan jawaban yang diterima, dengan satu sisi negatif: Ini membutuhkan pemeran. Dengan demikian, jawaban yang diterima lebih unggul.
Mafii
1
@Mafii Pemerannya murni operasi waktu kompilasi. Karena compiler tahu bahwa cast tidak dapat gagal, compiler tidak perlu memeriksa apa pun saat runtime. Perbedaan antara metode sepenuhnya estetika.
Pelayanan
8

Gunakan ReferenceEquals. Dari forum MSDN :

public static bool operator ==(Foo foo1, Foo foo2) {
    if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
    if (ReferenceEquals(foo2, null)) return false;
    return foo1.field1 == foo2.field2;
}
Jon Adams
sumber
4

Mencoba Object.ReferenceEquals(foo1, null)

Bagaimanapun, saya tidak akan merekomendasikan membebani ==operator secara berlebihan ; itu harus digunakan untuk membandingkan referensi, dan digunakan Equalsuntuk perbandingan "semantik".

Santiago Palladino
sumber
4

Jika saya telah mengganti bool Equals(object obj)dan saya ingin operator ==dan Foo.Equals(object obj)mengembalikan nilai yang sama, saya biasanya menerapkan !=operator seperti ini:

public static bool operator ==(Foo foo1, Foo foo2) {
  return object.Equals(foo1, foo2);
}
public static bool operator !=(Foo foo1, Foo foo2) {
  return !object.Equals(foo1, foo2);
}

Operator ==kemudian akan setelah melakukan semua pemeriksaan nol untuk saya akhirnya memanggil foo1.Equals(foo2)bahwa saya telah diganti untuk melakukan pemeriksaan sebenarnya jika keduanya sama.

Hallgrim
sumber
Ini terasa sangat tepat; melihat implementasi Object.Equals(Object, Object)berdampingan Object.ReferenceEquals(Object, Object), cukup jelas bahwa Object.Equals(Object, Object)melakukan semua seperti yang disarankan dalam jawaban lain di luar kotak. Mengapa tidak menggunakannya?
tne
@tne Karena tidak ada gunanya membebani ==operator jika yang Anda inginkan hanyalah perilaku default. Anda sebaiknya hanya membebani saat Anda perlu menerapkan logika perbandingan khusus, misalnya, sesuatu yang lebih dari sekadar pemeriksaan persamaan referensi.
Dan Bechard
@Dan saya yakin Anda salah memahami komentar saya; dalam konteks di mana sudah ditetapkan bahwa overloading ==diinginkan (pertanyaan menyiratkannya) Saya hanya mendukung jawaban ini dengan menyarankan bahwa Object.Equals(Object, Object)membuat trik lain seperti menggunakan ReferenceEqualsatau gips eksplisit tidak diperlukan (jadi "mengapa tidak menggunakannya?", "itu" menjadi Equals(Object, Object)). Bahkan jika tidak terkait poin Anda juga benar, dan saya akan melangkah lebih jauh: hanya membebani ==objek yang dapat kita klasifikasikan sebagai "objek nilai".
mentega
@tne Perbedaan utamanya adalah Object.Equals(Object, Object)pada gilirannya memanggil Object.Equals (Object) yang merupakan metode virtual yang mungkin ditimpa oleh Foo. Fakta bahwa Anda telah memasukkan panggilan virtual ke dalam pemeriksaan kesetaraan mungkin memengaruhi kemampuan penyusun untuk mengoptimalkan (misalnya sebaris) panggilan ini. Ini mungkin dapat diabaikan untuk sebagian besar tujuan, tetapi dalam kasus tertentu, biaya kecil dalam operator kesetaraan dapat berarti biaya yang sangat besar untuk loop atau struktur data yang diurutkan.
Dan Bechard
@tne Untuk informasi selengkapnya tentang seluk-beluk mengoptimalkan panggilan metode virtual, lihat stackoverflow.com/questions/530799/… .
Dan Bechard
3

Jika Anda menggunakan C # 7 atau yang lebih baru, Anda dapat menggunakan pencocokan pola konstan nol:

public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

Ini memberi Anda kode yang sedikit lebih rapi daripada yang memanggil objek .ReferenceEquals (foo1, null)

jacekbe.dll
sumber
2
ataupublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić
3

Sebenarnya ada cara yang lebih sederhana untuk memeriksa nulldalam kasus ini:

if (foo is null)

Itu dia!

Fitur ini diperkenalkan di C # 7

Reto Messerli
sumber
1

Pendekatan saya adalah melakukan

(object)item == null

di mana saya mengandalkan objectoperator kesetaraan sendiri yang tidak bisa salah. Atau metode ekstensi khusus (dan kelebihan beban):

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null;
}

public static bool IsNull<T>(this T? obj) where T : struct
{
    return !obj.HasValue;
}

atau untuk menangani lebih banyak kasus, mungkin:

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

Batasan mencegah IsNull jenis nilai. Sekarang semanis menelepon

object obj = new object();
Guid? guid = null; 
bool b = obj.IsNull(); // false
b = guid.IsNull(); // true
2.IsNull(); // error

yang berarti saya memiliki satu gaya konsisten / tidak-rawan kesalahan dalam memeriksa nulls di seluruh. Saya juga telah menemukan(object)item == null sangat sangat sangat sedikit lebih cepat daripadaObject.ReferenceEquals(item, null) , tetapi hanya jika itu penting (saya saat ini sedang mengerjakan sesuatu di mana saya telah mengoptimalkan mikro semuanya!).

Untuk melihat panduan lengkap tentang penerapan pemeriksaan kesetaraan, lihat Apa itu "Praktik Terbaik" Untuk Membandingkan Dua Contoh dari Jenis Referensi?

nawfal
sumber
Nitpick: Pembaca harus memperhatikan dependensi mereka sebelum melompat pada fitur seperti membandingkan DbNull, IMO kasus di mana ini tidak akan menghasilkan masalah yang terkait dengan SRP cukup jarang. Hanya menunjukkan bau kode, itu bisa sangat tepat.
tne
0

Equals(Object, Object)Metode statis menunjukkan apakah dua objek, objAdan objB, sama. Ini juga memungkinkan Anda untuk menguji objek yang nilainya nulluntuk persamaan. Ini membandingkan objAdan objBuntuk kesetaraan sebagai berikut:

  • Ini menentukan apakah dua objek mewakili referensi objek yang sama. Jika ya, metode tersebut akan kembali true. Tes ini setara dengan memanggil ReferenceEqualsmetode. Selain itu, jika keduanya objAdan objBadalah null, metode akan kembali true.
  • Ini menentukan apakah salah satu objAatau objBitu null. Jika demikian, itu kembali false. Jika dua objek tidak mewakili referensi objek yang sama dan tidak juga null, itu akan memanggil objA.Equals(objB)dan mengembalikan hasilnya. Artinya jika objAmenimpa Object.Equals(Object)metode, penggantian ini dipanggil.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}
Zach Posten
sumber
0

membalas lebih ke operator utama bagaimana membandingkan dengan null yang mengalihkan ke sini sebagai duplikat.

Dalam kasus di mana ini dilakukan untuk mendukung Objek Nilai, saya menemukan notasi baru berguna, dan ingin memastikan hanya ada satu tempat di mana perbandingan dibuat. Juga memanfaatkan Object.Equals (A, B) menyederhanakan pemeriksaan null.

Ini akan membebani ==,! =, Equals, dan GetHashCode

    public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
    public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
    public override bool Equals(object other) => Equals(other as ValueObject );
    public bool Equals(ValueObject other) {
        return !(other is null) && 
               // Value comparisons
               _value == other._value;
    }
    public override int GetHashCode() => _value.GetHashCode();

Untuk objek yang lebih rumit, tambahkan perbandingan tambahan di Equals dan GetHashCode yang lebih kaya.

CCondron
sumber
0

Untuk sintaksis modern dan ringkas:

public static bool operator ==(Foo x, Foo y)
{
    return x is null ? y is null : x.Equals(y);
}

public static bool operator !=(Foo x, Foo y)
{
    return x is null ? !(y is null) : !x.Equals(y);
}
mr5
sumber
-3

Sebuah kesalahan umum dalam overloads operator == adalah dengan menggunakan (a == b), (a ==null)atau (b == null)untuk memeriksa kesetaraan referensi. Ini malah menghasilkan panggilan ke operator yang kelebihan beban ==, menyebabkan infinite loop. Gunakan ReferenceEqualsatau transmisikan tipe ke Object, untuk menghindari pengulangan.

Lihat ini

// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
{
    return true;
}

// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))// using casting the type to Object
{
    return false;
}

referensi Panduan untuk Overloading Sama () dan Operator ==

Basheer AL-MOMANI
sumber
1
Sudah ada banyak jawaban dengan semua informasi ini. Kami tidak membutuhkan salinan ketujuh dari jawaban yang sama.
Pelayanan
-5

Anda dapat mencoba menggunakan properti objek dan menangkap NullReferenceException yang dihasilkan. Jika properti yang Anda coba diwarisi atau diganti dari Object, maka ini berfungsi untuk semua kelas.

public static bool operator ==(Foo foo1, Foo foo2)
{
    //  check if the left parameter is null
    bool LeftNull = false;
    try { Type temp = a_left.GetType(); }
    catch { LeftNull = true; }

    //  check if the right parameter is null
    bool RightNull = false;
    try { Type temp = a_right.GetType(); }
    catch { RightNull = true; }

    //  null checking results
    if (LeftNull && RightNull) return true;
    else if (LeftNull || RightNull) return false;
    else return foo1.field1 == foo2.field2;
}
Gabeg Digital
sumber
Jika Anda memiliki banyak objek null maka penanganan pengecualian mungkin menjadi overhead besar.
Kasprzol
2
Haha, saya setuju bahwa ini bukan metode terbaik. Setelah memposting metode ini, saya segera merevisi proyek saya saat ini untuk menggunakan ReferenceEquals. Namun, meskipun kurang optimal, ini berfungsi, dan dengan demikian merupakan jawaban yang valid untuk pertanyaan tersebut.
Digital Gabeg