Apa perbedaan antara IEqualityComparer <T> dan IEquatable <T>?

151

Saya ingin memahami skenario di mana IEqualityComparer<T>dan IEquatable<T>harus digunakan. Dokumentasi MSDN untuk keduanya terlihat sangat mirip.

Tilak
sumber
1
MSDN sez: "Antarmuka ini memungkinkan implementasi perbandingan kesetaraan khusus untuk koleksi. " Yang tentu saja disimpulkan dalam jawaban yang dipilih. MSDN juga merekomendasikan untuk mewarisi EqualityComparer<T>alih-alih mengimplementasikan antarmuka "karena EqualityComparer<T>menguji kesetaraan menggunakanIEquatable<T>
radarbob
... di atas menyarankan saya harus membuat koleksi khusus untuk Tpenerapan apa pun IEquatable<T>. Apakah koleksi ingin List<T>memiliki semacam bug halus di dalamnya?
radarbob
baik penjelasan anotherchris.net/csharp/...
Ramil Shavaleev
@RamilShavaleev tautannya rusak
ChessMax

Jawaban:

117

IEqualityComparer<T>adalah antarmuka untuk objek yang melakukan perbandingan pada dua objek jenis T.

IEquatable<T>adalah untuk objek bertipe Tsehingga dapat membandingkan dirinya dengan yang lain dari jenis yang sama.

Justin Niessner
sumber
1
Perbedaan yang sama adalah antara IComparable / IComparer
boctulus
3
Captain Obvious
Stepan Ivanenko
(Edited) IEqualityComparer<T>adalah sebuah antarmuka untuk suatu objek (yang biasanya kelas yang berbeda ringan dari T) yang menyediakan fungsi perbandingan yang beroperasi diT
rwong
61

Ketika memutuskan apakah akan menggunakan IEquatable<T>atau IEqualityComparer<T>, orang bisa bertanya:

Apakah ada cara yang disukai untuk menguji dua contoh Tuntuk kesetaraan, atau ada beberapa cara yang sama-sama valid?

  • Jika hanya ada satu cara menguji dua contoh Tuntuk kesetaraan, atau jika salah satu dari beberapa metode lebih disukai, maka IEquatable<T>akan menjadi pilihan yang tepat: Antarmuka ini seharusnya dilaksanakan hanya dengan Tsendirinya, sehingga satu contoh Tmemiliki pengetahuan internal tentang bagaimana membandingkan dirinya dengan contoh lain dari T.

  • Di sisi lain, jika ada beberapa metode yang sama-sama masuk akal membandingkan dua Tuntuk kesetaraan, IEqualityComparer<T>akan tampak lebih tepat: Antarmuka ini tidak dimaksudkan untuk diimplementasikan dengan Tsendirinya, tetapi oleh kelas "eksternal" lainnya. Oleh karena itu, ketika menguji dua contoh Tuntuk kesetaraan, karena Ttidak memiliki pemahaman internal tentang kesetaraan, Anda harus membuat pilihan eksplisit dari IEqualityComparer<T>contoh yang melakukan pengujian sesuai dengan persyaratan spesifik Anda.

Contoh:

Mari kita pertimbangkan dua jenis ini (yang seharusnya memiliki semantik nilai ):

interface IIntPoint : IEquatable<IIntPoint>
{
    int X { get; }
    int Y { get; }
}

interface IDoublePoint  // does not inherit IEquatable<IDoublePoint>; see below.
{
    double X { get; }
    double Y { get; }
}

Mengapa hanya salah satu dari jenis ini yang mewarisi IEquatable<>, tetapi tidak yang lain?

Secara teori, hanya ada satu cara yang masuk akal untuk membandingkan dua contoh dari kedua jenis: Mereka sama jika Xdan Yproperti di kedua contoh sama. Menurut pemikiran ini, kedua jenis harus diterapkan IEquatable<>, karena tampaknya tidak ada cara lain yang berarti untuk melakukan tes kesetaraan.

Masalahnya di sini adalah bahwa membandingkan angka floating-point untuk kesetaraan mungkin tidak berfungsi seperti yang diharapkan, karena kesalahan pembulatan menit . Ada beberapa metode yang berbeda untuk membandingkan angka floating-point untuk hampir-kesetaraan , masing-masing dengan keunggulan dan pengorbanan tertentu, dan Anda mungkin ingin dapat memilih sendiri metode mana yang tepat.

sealed class DoublePointNearEqualityComparerByTolerance : IEqualityComparer<IDoublePoint>
{
    public DoublePointNearEqualityComparerByTolerance(double tolerance) {  }
    
    public bool Equals(IDoublePoint a, IDoublePoint b)
    {
        return Math.Abs(a.X - b.X) <= tolerance  &&  Math.Abs(a.Y - b.Y) <= tolerance;
    }
    
}

Perhatikan bahwa halaman yang saya tautkan (di atas) secara eksplisit menyatakan bahwa tes untuk hampir-kesetaraan ini memiliki beberapa kelemahan. Karena ini adalah IEqualityComparer<T>implementasi, Anda bisa menukar saja jika itu tidak cukup baik untuk tujuan Anda.

stakx - tidak lagi berkontribusi
sumber
6
Metode perbandingan yang disarankan untuk menguji kesetaraan titik ganda rusak, karena setiap IEqualityComparer<T>implementasi yang sah harus menerapkan hubungan ekivalensi, yang menyiratkan bahwa dalam semua kasus di mana dua objek membandingkan sama dengan ketiga, mereka harus membandingkan sama satu sama lain. Setiap kelas yang mengimplementasikan IEquatable<T>atau IEqualityComparer<T>dengan cara yang bertentangan dengan di atas rusak.
supercat
5
Contoh yang lebih baik adalah perbandingan antara string. Masuk akal untuk memiliki metode perbandingan string yang menganggap string sama dengan hanya jika mereka mengandung urutan byte yang sama, tetapi ada metode perbandingan berguna lainnya yang juga merupakan hubungan ekivalensi , seperti perbandingan case-insensitive. Perhatikan bahwa IEqualityComparer<String>yang menganggap "halo" sama dengan "Halo" dan "hElLo" harus menganggap "Halo" dan "hElLo" sama satu sama lain, tetapi untuk sebagian besar metode perbandingan yang tidak akan menjadi masalah.
supercat
@supercat, terima kasih atas umpan balik yang berharga. Kemungkinan saya tidak akan memperbaiki jawaban saya dalam beberapa hari ke depan, jadi jika Anda mau, silakan edit sesuka Anda.
stakx - tidak lagi berkontribusi
Inilah yang saya butuhkan dan apa yang saya lakukan. Namun ada kebutuhan untuk mengganti GetHashCode, di mana ia akan mengembalikan false setelah nilainya berbeda. Bagaimana Anda menangani GetHashCode Anda?
teapeng
@teapeng: Tidak yakin kasus penggunaan spesifik apa yang Anda bicarakan. Secara umum, GetHashCodeseharusnya Anda dapat dengan cepat menentukan apakah dua nilai berbeda. Aturannya kira-kira sebagai berikut: (1) GetHashCode harus selalu menghasilkan kode hash yang sama untuk nilai yang sama. (2) GetHashCode harus cepat (lebih cepat dari Equals). (3) GetHashCode tidak harus tepat (tidak setepat Equals). Ini berarti dapat menghasilkan kode hash yang sama untuk nilai yang berbeda. Semakin tepat Anda dapat membuatnya, semakin baik, tetapi mungkin lebih penting untuk membuatnya cepat.
stakx - tidak lagi berkontribusi
27

Anda sudah mendapatkan definisi dasar tentang apa itu . Singkatnya, jika Anda menerapkan IEquatable<T>pada kelas T, Equalsmetode pada objek bertipe Tmemberitahu Anda jika objek itu sendiri (yang sedang diuji untuk kesetaraan) sama dengan contoh lain dari jenis yang sama T. Sedangkan, IEqualityComparer<T>adalah untuk menguji kesetaraan dua contoh T, biasanya di luar lingkup contoh T.

Untuk apa mereka bisa membingungkan pada awalnya. Dari definisi itu harus jelas bahwa karenanya IEquatable<T>(didefinisikan dalam kelas Titu sendiri) harus menjadi standar de facto untuk mewakili keunikan objek / instansnya. HashSet<T>, Dictionary<T, U>(Mengingat GetHashCodeditimpa juga), Containsdi List<T>dll make penggunaan ini. Melaksanakan IEqualityComparer<T>pada Ttidak membantu kasus-kasus umum yang disebutkan di atas. Selanjutnya, ada sedikit nilai untuk diterapkan IEquatable<T>pada kelas lain selain T. Ini:

class MyClass : IEquatable<T>

jarang masuk akal.

Di samping itu

class T : IEquatable<T>
{
    //override ==, !=, GetHashCode and non generic Equals as well

    public bool Equals(T other)
    {
        //....
    }
}

adalah bagaimana hal itu harus dilakukan.

IEqualityComparer<T>dapat bermanfaat saat Anda memerlukan validasi khusus kesetaraan, tetapi tidak sebagai aturan umum. Misalnya, dalam kelas Persondi beberapa titik Anda mungkin perlu menguji kesetaraan dua orang berdasarkan usia mereka. Dalam hal ini Anda dapat melakukan:

class Person
{
    public int Age;
}

class AgeEqualityTester : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.Age == y.Age;
    }

    public int GetHashCode(Person obj)
    {
        return obj.Age.GetHashCode;
    }
}

Untuk mengujinya, coba

var people = new Person[] { new Person { age = 23 } };
Person p = new Person() { age = 23 };

print people.Contains(p); //false;
print people.Contains(p, new AgeEqualityTester()); //true

Demikian pula IEqualityComparer<T>pada Ttidak masuk akal.

class Person : IEqualityComparer<Person>

Benar ini bekerja, tetapi tidak terlihat baik di mata dan mengalahkan logika.

Biasanya yang Anda butuhkan adalah IEquatable<T>. Idealnya, Anda hanya dapat memiliki satu IEquatable<T>sementara beberapa IEqualityComparer<T>dimungkinkan berdasarkan kriteria yang berbeda.

Itu IEqualityComparer<T>dan IEquatable<T>persis analog dengan Comparer<T>dan IComparable<T>yang digunakan untuk tujuan perbandingan daripada menyamakan; thread yang bagus di sini di mana saya menulis jawaban yang sama :)

nawfal
sumber
public int GetHashCode(Person obj)harus kembaliobj.GetHashCode()
hyankov
@HristoYankov harus kembali obj.Age.GetHashCode. akan diedit.
nawfal
Tolong jelaskan mengapa pembanding harus membuat asumsi tentang apa Hash objek yang dibandingkan? Pembanding Anda tidak tahu bagaimana Orang menghitung Hash-nya, mengapa Anda menimpanya?
hyankov
@HristoYankov Pembanding Anda tidak tahu bagaimana Orang menghitung Hash - Tentu, itu sebabnya kami belum secara langsung menelepon ke person.GetHashCodemana saja .... mengapa Anda menimpanya? - Kami mengesampingkan karena intinya IEqualityCompareradalah untuk memiliki implementasi perbandingan yang berbeda sesuai dengan aturan kami - aturan yang kami ketahui dengan baik.
nawfal
Dalam contoh saya, saya perlu mendasarkan perbandingan dari Ageproperti, jadi saya menelepon Age.GetHashCode. Umur adalah tipe int, tetapi apa pun jenisnya adalah kontrak kode hash di .NET adalah itu different objects can have equal hash but equal objects cant ever have different hashes. Ini adalah kontrak yang bisa kita percayai secara membuta. Tentu pembanding khusus kami harus mematuhi aturan ini juga. Jika pernah memanggil someObject.GetHashCodehal-hal istirahat, maka itu adalah masalah dengan implementor tipe someObject, itu bukan masalah kita.
nawfal
11

IEqualityComparer adalah untuk digunakan ketika kesetaraan dua objek diimplementasikan secara eksternal, misalnya jika Anda ingin mendefinisikan pembanding untuk dua jenis yang Anda tidak memiliki sumbernya, atau untuk kasus di mana kesetaraan antara dua hal hanya masuk akal dalam konteks terbatas.

IEquatable adalah untuk mengimplementasikan objek itu sendiri (yang dibandingkan untuk kesetaraan).

Chris Shain
sumber
Anda adalah satu-satunya yang benar-benar mengangkat masalah "Memasukkan Kesetaraan" (penggunaan eksternal). plus 1.
Royi Namir
4

Satu membandingkan dua T. Yang lain dapat membandingkan dirinya dengan yang lain T. Biasanya, Anda hanya perlu menggunakannya satu per satu, bukan keduanya.

Matt Ball
sumber