วิธีใช้ IEqualityComparer


101

ฉันมีระฆังในฐานข้อมูลที่มีหมายเลขเดียวกัน ฉันต้องการรับทั้งหมดโดยไม่ต้องทำซ้ำ ฉันสร้างคลาสเปรียบเทียบเพื่อทำงานนี้ แต่การดำเนินการของฟังก์ชันทำให้เกิดความล่าช้าอย่างมากจากฟังก์ชันโดยไม่แตกต่างจาก 0.6 วินาทีถึง 3.2 วินาที!

ฉันทำถูกต้องหรือฉันต้องใช้วิธีอื่น?

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;
    }
}

16
คุณอาจต้องการดูแนวทางและกฎสำหรับ GetHashCode
Conrad Frix

บล็อกนี้อธิบายถึงวิธีการใช้ IEqualityComparer อย่างสมบูรณ์แบบ: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

คำตอบ:


177

GetHashCodeการใช้งานของคุณจะส่งคืนค่าเดียวกันเสมอ Distinctอาศัยฟังก์ชันแฮชที่ดีเพื่อให้ทำงานได้อย่างมีประสิทธิภาพเนื่องจากสร้างตารางแฮชไว้ภายใน

เมื่อใช้อินเทอร์เฟซของคลาสสิ่งสำคัญคือต้องอ่านเอกสารประกอบเพื่อให้ทราบว่าคุณควรทำสัญญาใด 1

ในโค้ดของคุณวิธีแก้ปัญหาคือการส่งGetHashCodeต่อClass_reglement.Numf.GetHashCodeและนำไปใช้อย่างเหมาะสมที่นั่น

นอกเหนือจากนั้นEqualsวิธีการของคุณยังเต็มไปด้วยโค้ดที่ไม่จำเป็น สามารถเขียนใหม่ได้ดังนี้ (ความหมายเดียวกัน, ¼ของรหัส, อ่านได้มากขึ้น):

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

สุดท้ายการToListโทรไม่จำเป็นและใช้เวลานาน: AddRangeยอมรับIEnumerableการแปลงเป็น a Listไม่จำเป็น AsEnumerableจะยังซ้ำซ้อนที่นี่ตั้งแต่การประมวลผลผลในการAddRangeที่จะทำให้อยู่แล้วนี้


1การเขียนรหัสโดยไม่ทราบว่าสิ่งที่มันจริงไม่เรียกว่าการเขียนโปรแกรมการขนส่งสินค้าทางศาสนา เป็นการปฏิบัติที่แพร่หลายอย่างน่าประหลาดใจ โดยพื้นฐานแล้วไม่ได้ผล


20
Equals ของคุณล้มเหลวเมื่อ x หรือ y เป็นโมฆะ
dzendras

4
@dzendras เช่นเดียวกันสำหรับGetHashCode. อย่างไรก็ตามโปรดทราบว่าเอกสารประกอบIEqualityComparer<T>ไม่ได้ระบุว่าจะทำอย่างไรกับnullอาร์กิวเมนต์ - แต่ตัวอย่างที่ให้ไว้ในบทความไม่ได้จัดการnullเช่นกัน
Konrad Rudolph

50
ว้าว. ความสะอิดสะเอียนรุนแรงโดยไม่จำเป็น เราอยู่ที่นี่เพื่อช่วยกันไม่ดูถูก ฉันเดาว่ามันทำให้บางคนหัวเราะ แต่ฉันขอแนะนำให้ลบออก
Jess

5
+1 สำหรับการทำให้ฉันอ่านเกี่ยวกับ "การเขียนโปรแกรมลัทธิการขนส่งสินค้า" ในวิกิแล้วเปลี่ยนบรรทัดแท็ก skype ของฉันเป็น "// เวทมนตร์ล้ำลึกเริ่มต้นที่นี่ ... ตามด้วยเวทมนตร์ที่หนักหน่วง"
Alex

4
@NeilBenn คุณผิดคำแนะนำตรงไปตรงมาสำหรับความหยาบคาย เนื่องจาก OP ยอมรับคำตอบ (และฉันอาจสังเกตว่าในเวอร์ชั่นที่เข้มงวดกว่ามาก!) ดูเหมือนว่าพวกเขาจะไม่ได้ทำผิดแบบเดียวกัน ฉันไม่แน่ใจว่าทำไมคุณถึงคิดว่าการให้คำแนะนำเป็นเรื่องที่หยาบคาย แต่คุณเข้าใจผิดเมื่อพูดว่า“ ผู้ชายไม่ต้องการการบรรยาย” ฉันไม่เห็นด้วยอย่างยิ่ง: การบรรยายเป็นสิ่งจำเป็นและต้องคำนึงถึง รหัสตามที่เขียนนั้นไม่ดีและมีพื้นฐานมาจากการปฏิบัติงานที่ไม่ดี การไม่ชี้ว่าสิ่งนี้จะเป็นการทำลายและไม่เป็นประโยชน์เลยตั้งแต่นั้น OP ไม่สามารถปรับปรุงวิธีการทำงานได้
Konrad Rudolph

49

ลองใช้รหัสนี้:

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();
    }
}

ตัวอย่างการใช้งานจะเป็น

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 

20
GetHashCodeจำเป็นต้องใช้นิพจน์ด้วย: return _expr.Invoke(obj).GetHashCode();ดูโพสต์นี้สำหรับการใช้งาน
orad

3

เพียงแค่เขียนโค้ดพร้อมการใช้งานGetHashCodeและNULLการตรวจสอบความถูกต้อง:

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;
    }
}

ตัวอย่าง: รายการClass_reglement ที่แตกต่างกันโดย Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());

2

การรวมคลาสการเปรียบเทียบของคุณ (หรือโดยเฉพาะอย่างยิ่งการAsEnumerableโทรที่คุณต้องใช้เพื่อให้มันทำงานได้) หมายความว่าตรรกะการเรียงลำดับเปลี่ยนจากการใช้เซิร์ฟเวอร์ฐานข้อมูลไปเป็นการอยู่บนไคลเอนต์ฐานข้อมูล (แอปพลิเคชันของคุณ) นั่นหมายความว่าลูกค้าของคุณจำเป็นต้องดึงข้อมูลและประมวลผลระเบียนจำนวนมากขึ้นซึ่งจะมีประสิทธิภาพน้อยกว่าในการค้นหาฐานข้อมูลที่สามารถใช้ดัชนีเปรียบเทียบได้

คุณควรพยายามพัฒนา where clause ที่ตรงตามความต้องการของคุณแทนโปรดดูการใช้ IEqualityComparer กับ LINQ to Entities except clauseสำหรับรายละเอียดเพิ่มเติม


2

หากคุณต้องการโซลูชันทั่วไปที่ไม่มีมวย:

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);
    }
}

การใช้งาน:

KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf)

0

IEquatable<T> อาจเป็นวิธีที่ง่ายกว่ามากในการใช้เฟรมเวิร์กสมัยใหม่

คุณมีbool Equals(T other)ฟังก์ชั่นง่ายๆที่ดีและไม่ต้องวุ่นวายกับการแคสต์หรือสร้างคลาสแยกกัน

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 
    }
}

หมายเหตุ: คุณต้องใช้ถ้าใช้นี้ในพจนานุกรมหรือกับสิ่งที่ต้องการGetHashCodeDistinct

ปล. ฉันไม่คิดว่าเมธอด Equals ที่กำหนดเองใด ๆ จะทำงานกับเอนทิตีเฟรมเวิร์กโดยตรงทางด้านฐานข้อมูล (ฉันคิดว่าคุณรู้สิ่งนี้เพราะคุณทำ AsEnumerable) แต่นี่เป็นวิธีที่ง่ายกว่ามากในการทำ Equals แบบง่ายสำหรับกรณีทั่วไป

หากสิ่งต่างๆดูเหมือนจะไม่ทำงาน (เช่นข้อผิดพลาดของคีย์ที่ซ้ำกันเมื่อทำ ToDictionary) ให้ใส่เบรกพอยต์ไว้ใน Equals เพื่อให้แน่ใจว่าได้รับผลกระทบและตรวจสอบให้แน่ใจว่าคุณได้GetHashCodeกำหนด (ด้วยคีย์เวิร์ดแทนที่)


1
คุณยังต้องตรวจสอบ null
disklosr

ฉันไม่เคยเจอเรื่องนั้น แต่ฉันจะจำไว้ว่าจะทำเช่นนั้นในครั้งต่อไป คุณมีค่าว่างในรายการ <T> หรืออะไรทำนองนั้น?
Simon_Weaver

1
ภายใต้.Equals()วิธีการที่คุณดูเหมือนจะเปรียบเทียบother.Hometownกับตัวมันเองแทนที่จะเป็นthis.Hometown
Jake Stokes

อ๊ะ. แก้ไขพิมพ์ผิด :)
Simon_Weaver
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.