Cara menggunakan IEqualityComparer

97

Saya memiliki beberapa lonceng di database saya dengan nomor yang sama. Saya ingin mendapatkan semuanya tanpa duplikasi. Saya membuat kelas pembanding untuk melakukan pekerjaan ini, tetapi eksekusi fungsi menyebabkan penundaan besar dari fungsi tanpa perbedaan, dari 0,6 detik menjadi 3,2 detik!

Apakah saya melakukannya dengan benar atau apakah saya harus menggunakan metode lain?

reg.AddRange(
    (from a in this.dataContext.reglements
     join b in this.dataContext.Clients on a.Id_client equals b.Id
     where a.date_v <= datefin && a.date_v >= datedeb
     where a.Id_client == b.Id
     orderby a.date_v descending 
     select new Class_reglement
     {
         nom  = b.Nom,
         code = b.code,
         Numf = a.Numf,
     })
    .AsEnumerable()
    .Distinct(new Compare())
    .ToList());

class Compare : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x.Numf == y.Numf)
        {
            return true;
        }
        else { return false; }
    }
    public int GetHashCode(Class_reglement codeh)
    {
        return 0;
    }
}
Akrem
sumber
16
Anda mungkin ingin melihat Panduan dan aturan untuk GetHashCode
Conrad Frix
Blog ini menjelaskan cara menggunakan IEqualityComparer dengan sempurna: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Jawaban:

174

GetHashCodePenerapan Anda selalu menghasilkan nilai yang sama. Distinctbergantung pada fungsi hash yang baik untuk bekerja secara efisien karena secara internal membangun tabel hash .

Saat mengimplementasikan antarmuka kelas, penting untuk membaca dokumentasinya , untuk mengetahui kontrak mana yang harus Anda implementasikan. 1

Dalam kode Anda, solusinya adalah untuk meneruskan GetHashCodeke Class_reglement.Numf.GetHashCodedan menerapkannya tepat di sana.

Selain itu, Equalsmetode Anda penuh dengan kode yang tidak perlu. Itu bisa ditulis ulang sebagai berikut (semantik yang sama, ¼ dari kode, lebih mudah dibaca):

public bool Equals(Class_reglement x, Class_reglement y)
{
    return x.Numf == y.Numf;
}

Terakhir, ToListpanggilan tidak perlu dan memakan waktu: AddRangemenerima apa pun IEnumerablesehingga konversi ke a Listtidak diperlukan. AsEnumerableadalah juga membazir di sini sejak pengolahan hasil di AddRangeakan menyebabkan ini pula.


1 Menulis kode tanpa mengetahui apa yang sebenarnya dilakukannya disebut pemrograman kultus kargo . Ini adalah praktik yang sangat tersebar luas. Ini pada dasarnya tidak berhasil.

Konrad Rudolph
sumber
20
Hasil Sama Anda gagal jika x atau y adalah nol.
dzendras
4
@dzendras Sama untuk GetHashCode. Namun, perhatikan bahwa dokumentasiIEqualityComparer<T> tidak menentukan apa yang harus dilakukan dengan nullargumen - tetapi contoh yang diberikan dalam artikel juga tidak menangani null.
Konrad Rudolph
50
Wow. Kekejian tidak perlu kasar. Kami di sini untuk saling membantu, bukan menghina. Saya rasa itu membuat beberapa orang tertawa, tapi saya akan merekomendasikan untuk menghapusnya.
Jess
5
+1 untuk membuat saya membaca tentang "pemrograman kultus kargo" di wiki dan kemudian mengubah baris tag skype saya menjadi "// Sihir mendalam dimulai di sini ... diikuti oleh beberapa sihir berat".
Alex
4
@NeilBenn Anda salah mengira nasihat yang terus terang adalah kekasaran. Sejak OP menerima jawabannya (dan, saya mungkin mencatat, dalam versi yang jauh lebih tegas!), Mereka tampaknya tidak membuat kesalahan yang sama. Saya tidak yakin mengapa Anda berpikir bahwa memberi nasihat itu tidak sopan, tetapi Anda salah ketika mengatakan bahwa “orang itu tidak membutuhkan kuliah”. Saya sangat tidak setuju: ceramah itu diperlukan, dan itu dimasukkan ke dalam hati. Kode, seperti yang tertulis, buruk, dan didasarkan pada praktik kerja yang buruk. Tidak menunjukkan hal ini akan merugikan, dan sama sekali tidak membantu, sejak itu OP tidak dapat meningkatkan cara kerjanya.
Konrad Rudolph
47

Coba kode ini:

public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericCompare(Func<T, object> expr)
    {
        this._expr = expr;
    }
    public bool Equals(T x, T y)
    {
        var first = _expr.Invoke(x);
        var sec = _expr.Invoke(y);
        if (first != null && first.Equals(sec))
            return true;
        else
            return false;
    }
    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }
}

Contoh penggunaannya adalah

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 
suneelsarraf.dll
sumber
19
GetHashCodeperlu menggunakan ekspresi juga: return _expr.Invoke(obj).GetHashCode();Lihat posting ini untuk penggunaan itu.
orad
3

Cukup kode, dengan implementasi GetHashCodedan NULLvalidasi:

public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x is null || y is null))
            return false;

        return x.Numf == y.Numf;
    }

    public int GetHashCode(Class_reglement product)
    {
        //Check whether the object is null 
        if (product is null) return 0;

        //Get hash code for the Numf field if it is not null. 
        int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();

        return hashNumf;
    }
}

Contoh: daftar Class_reglement yang dibedakan oleh Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());
Shahar Shokrani
sumber
2

Dimasukkannya kelas perbandingan Anda (atau lebih khusus lagi AsEnumerablepanggilan yang perlu Anda gunakan untuk membuatnya berfungsi) berarti bahwa logika pengurutan berubah dari yang berbasis pada server basis data menjadi pada klien basis data (aplikasi Anda). Ini berarti bahwa klien Anda sekarang perlu mengambil dan kemudian memproses data dalam jumlah yang lebih besar, yang akan selalu kurang efisien daripada melakukan pencarian di database tempat indeks yang sesuai dapat digunakan.

Anda harus mencoba mengembangkan klausa where yang memenuhi persyaratan Anda, lihat Menggunakan IEqualityComparer dengan klausa LINQ ke Entitas Kecuali untuk detail selengkapnya.

Justin
sumber
2

Jika Anda menginginkan solusi umum tanpa tinju:

public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    private readonly Func<T, TKey> _keyGetter;

    public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
    {
        _keyGetter = keyGetter;
    }

    public bool Equals(T x, T y)
    {
        return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
    }

    public int GetHashCode(T obj)
    {
        TKey key = _keyGetter(obj);

        return key == null ? 0 : key.GetHashCode();
    }
}

public static class KeyBasedEqualityComparer<T>
{
    public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
    {
        return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
    }
}

pemakaian:

KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf)
pengguna764754
sumber
0

IEquatable<T> bisa menjadi cara yang jauh lebih mudah untuk melakukan ini dengan kerangka kerja modern.

Anda mendapatkan bool Equals(T other)fungsi sederhana yang bagus dan tidak ada masalah dengan casting atau membuat kelas terpisah.

public class Person : IEquatable<Person>
{
    public Person(string name, string hometown)
    {
        this.Name = name;
        this.Hometown = hometown;
    }

    public string Name { get; set; }
    public string Hometown { get; set; }

    // can't get much simpler than this!
    public bool Equals(Person other)
    {
        return this.Name == other.Name && this.Hometown == other.Hometown;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();  // see other links for hashcode guidance 
    }
}

Catatan Anda HARUS menerapkan GetHashCodejika menggunakan ini dalam kamus atau dengan sesuatu seperti Distinct.

PS. Saya tidak berpikir ada metode Setara kustom yang bekerja dengan kerangka entitas langsung di sisi database (saya pikir Anda tahu ini karena Anda melakukan AsEnumerable) tetapi ini adalah metode yang jauh lebih sederhana untuk melakukan persamaan sederhana untuk kasus umum.

Jika hal-hal tampaknya tidak berfungsi (seperti kesalahan kunci duplikat saat melakukan ToDictionary) letakkan titik putus di dalam Sama dengan untuk memastikan itu terpukul dan pastikan Anda telah GetHashCodemenentukan (dengan mengganti kata kunci).

Simon_Weaver
sumber
1
Anda masih perlu memeriksa null
disklosr
Saya tidak pernah mengalami itu tetapi saya akan ingat untuk melakukannya lain kali. Apakah Anda memiliki null dalam List <T> atau sesuatu seperti itu?
Simon_Weaver
1
Di bawah .Equals()metode Anda tampaknya telah dibandingkan other.Hometowndengan dirinya sendiri, alih-alihthis.Hometown
Jake Stokes
Ups. Memperbaiki kesalahan ketik :)
Simon_Weaver