Beberapa fungsi Linq. Dapat dihitung IEqualityComparer<T>
. Apakah ada kelas pembungkus yang nyaman yang mengadaptasi delegate(T,T)=>bool
untuk mengimplementasikan IEqualityComparer<T>
? Cukup mudah untuk menulis satu (jika Anda mengabaikan masalah dengan mendefinisikan kode hash yang benar), tetapi saya ingin tahu apakah ada solusi out-of-the-box.
Secara khusus, saya ingin melakukan operasi set pada Dictionary
s, hanya menggunakan Kunci untuk menentukan keanggotaan (sambil mempertahankan nilai-nilai sesuai dengan aturan yang berbeda).
IEqualityComparer<T>
yangGetHashCode
keluar akan langsung rusak.Tentang pentingnya
GetHashCode
Yang lain sudah berkomentar tentang fakta bahwa
IEqualityComparer<T>
implementasi kustom harus benar-benar menyertakanGetHashCode
metode ; tapi tidak ada yang mau repot-repot menjelaskan alasannya .Inilah sebabnya. Pertanyaan Anda secara khusus menyebutkan metode ekstensi LINQ; hampir semua ini mengandalkan kode hash untuk bekerja dengan baik, karena mereka menggunakan tabel hash secara internal untuk efisiensi.
Ambil
Distinct
contohnya. Pertimbangkan implikasi metode ekstensi ini jika semua yang digunakan adalahEquals
metode. Bagaimana Anda menentukan apakah suatu item sudah dipindai secara berurutan jika hanya dimilikiEquals
? Anda menghitung seluruh koleksi nilai yang telah Anda lihat dan memeriksa kecocokan. Ini akan menghasilkanDistinct
penggunaan algoritma O (N 2 ) kasus terburuk alih-alih O (N)!Untungnya, ini bukan masalahnya.
Distinct
tidak hanya digunakanEquals
; itu menggunakanGetHashCode
juga. Bahkan, itu benar - benar tidak berfungsi sebagaimana mestinya tanpaIEqualityComparer<T>
pasokan yang tepatGetHashCode
. Di bawah ini adalah contoh buat menggambarkan ini.Katakanlah saya memiliki tipe berikut:
Sekarang katakan saya punya
List<Value>
dan saya ingin menemukan semua elemen dengan nama yang berbeda. Ini adalah kasus penggunaan yang sempurna untukDistinct
menggunakan pembanding kesetaraan khusus. Jadi mari kita gunakanComparer<T>
kelas dari jawaban Aku :Sekarang, jika kita memiliki banyak
Value
elemen denganName
properti yang sama , semuanya harus runtuh menjadi satu nilai yang dikembalikanDistinct
, kan? Ayo lihat...Keluaran:
Hmm, itu tidak berhasil, kan?
Bagaimana dengan
GroupBy
? Mari kita coba itu:Keluaran:
Lagi: tidak berhasil.
Jika Anda memikirkannya, masuk akal untuk
Distinct
menggunakanHashSet<T>
(atau setara) secara internal, dan untukGroupBy
menggunakan sesuatu sepertiDictionary<TKey, List<T>>
internal. Bisakah ini menjelaskan mengapa metode ini tidak berhasil? Mari kita coba ini:Keluaran:
Ya ... mulai masuk akal?
Semoga dari contoh-contoh ini jelas mengapa memasukkan yang sesuai
GetHashCode
dalamIEqualityComparer<T>
implementasi apa pun begitu penting.Jawaban asli
Memperluas jawaban orip :
Ada beberapa perbaikan yang bisa dilakukan di sini.
Func<T, TKey>
alih-alihFunc<T, object>
; ini akan mencegah tinju kunci tipe nilai dalamkeyExtractor
dirinya sendiri.where TKey : IEquatable<TKey>
batasan; ini akan mencegah tinju dalamEquals
panggilan (object.Equals
mengambilobject
parameter; Anda perluIEquatable<TKey>
implementasi untuk mengambilTKey
parameter tanpa tinju). Jelas ini bisa menimbulkan batasan yang terlalu berat, sehingga Anda bisa membuat kelas dasar tanpa batasan dan kelas turunan dengannya.Seperti apa bentuk kode yang dihasilkan:
sumber
StrictKeyEqualityComparer.Equals
Metode Anda tampaknya sama denganKeyEqualityComparer.Equals
. ApakahTKey : IEquatable<TKey>
kendala membuatTKey.Equals
pekerjaan berbeda?TKey
mungkin jenis sembarang, kompiler akan menggunakan metode virtualObject.Equals
yang akan memerlukan tinju parameter tipe nilai, misalnyaint
,. Dalam kasus terakhir, bagaimanapun, karenaTKey
dibatasi untuk mengimplementasikanIEquatable<TKey>
,TKey.Equals
metode akan digunakan yang tidak memerlukan tinju.StringKeyEqualityComparer<T, TKey>
juga.Saat Anda ingin menyesuaikan pengecekan kesetaraan, 99% waktu Anda tertarik untuk mendefinisikan kunci untuk dibandingkan, bukan perbandingan itu sendiri.
Ini bisa menjadi solusi yang elegan (konsep dari metode semacam daftar Python ).
Pemakaian:
The
KeyEqualityComparer
kelas:sumber
Func<T, int>
untuk memasok kode hash untukT
nilai (seperti yang telah disarankan dalam, misalnya, jawaban Ruben ). Kalau tidak,IEqualityComparer<T>
implementasi yang tersisa cukup rusak, terutama yang berkaitan dengan kegunaannya dalam metode ekstensi LINQ. Lihat jawaban saya untuk diskusi tentang mengapa ini.Saya khawatir tidak ada bungkus seperti itu di luar kotak. Namun tidak sulit untuk membuatnya:
sumber
private readonly Func<T, int> _hashCodeResolver
yang juga harus dilewatkan di konstruktor dan digunakan dalamGetHashCode(...)
metode.obj.ToString().ToLower().GetHashCode()
bukanobj.GetHashCode()
?IEqualityComparer<T>
selalu menggunakan hashing di belakang layar (misalnya, GroupBy, Distinct, Kecuali, Bergabung, dll LINQ's) dan kontrak MS tentang hashing rusak dalam implementasi ini. Berikut kutipan dokumentasi MS: "Implementasi diperlukan untuk memastikan bahwa jika metode Persamaan mengembalikan nilai true untuk dua objek x dan y, maka nilai yang dikembalikan oleh metode GetHashCode untuk x harus sama dengan nilai yang dikembalikan untuk y." Lihat: msdn.microsoft.com/en-us/library/ms132155Sama seperti jawaban Dan Tao, tetapi dengan beberapa peningkatan:
Mengandalkan
EqualityComparer<>.Default
melakukan perbandingan aktual sehingga menghindari tinju untuk jenis nilaistruct
yang telah diterapkanIEquatable<>
.Sejak
EqualityComparer<>.Default
digunakan itu tidak meledaknull.Equals(something)
.Disediakan pembungkus statis di sekitar
IEqualityComparer<>
yang akan memiliki metode statis untuk membuat instance dari pembanding - memudahkan panggilan. Membandingkandengan
Menambahkan kelebihan untuk menentukan
IEqualityComparer<>
kunci.Kelas:
Anda dapat menggunakannya seperti ini:
Orang adalah kelas sederhana:
sumber
Dengan ekstensi: -
sumber
jawaban orip itu bagus.
Berikut sedikit metode ekstensi untuk membuatnya lebih mudah:
sumber
Saya akan menjawab pertanyaan saya sendiri. Untuk memperlakukan Kamus sebagai set, metode paling sederhana tampaknya menerapkan operasi set ke dict.Keys, lalu konversikan kembali ke Kamus dengan Enumerable.ToDictionary (...).
sumber
Implementasi di (teks Jerman) Menerapkan IEqualityCompare dengan ekspresi lambda peduli tentang nilai nol dan menggunakan metode ekstensi untuk menghasilkan IEqualityComparer.
Untuk membuat IEqualityComparer dalam serikat Linq, Anda hanya perlu menulis
Pembanding:
Anda juga perlu menambahkan metode ekstensi untuk mendukung inferensi tipe
sumber
Hanya satu pengoptimalan: Kita dapat menggunakan EqualityComparer out-of-the-box untuk perbandingan nilai, daripada mendelegasikannya.
Ini juga akan membuat implementasi lebih bersih karena logika perbandingan aktual sekarang berada di GetHashCode () dan Equals () yang mungkin sudah Anda overload.
Ini kodenya:
Jangan lupa untuk membebani metode GetHashCode () dan Equals () pada objek Anda.
Posting ini membantu saya: c # membandingkan dua nilai generik
Sushil
sumber
jawaban orip itu bagus. Memperluas jawaban orip:
saya pikir kunci solusinya adalah menggunakan "Metode Perpanjangan" untuk mentransfer "jenis anonim".
Pemakaian:
sumber
Ini memungkinkan untuk memilih properti dengan lambda seperti ini:
.Select(y => y.Article).Distinct(x => x.ArticleID);
sumber
Saya tidak tahu kelas yang ada tetapi sesuatu seperti:
Catatan: Saya belum mengkompilasi dan menjalankan ini, jadi mungkin ada kesalahan ketik atau bug lainnya.
sumber