Cách sử dụng IEqualityComparer


97

Tôi có một số chuông trong cơ sở dữ liệu của mình với cùng một số. Tôi muốn lấy tất cả chúng mà không bị trùng lặp. Tôi đã tạo một lớp so sánh để thực hiện công việc này, nhưng việc thực thi hàm gây ra độ trễ lớn từ hàm mà không có sự khác biệt, từ 0,6 giây đến 3,2 giây!

Tôi đang làm đúng hay tôi phải sử dụng phương pháp khác?

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


Blog này giải thích cách sử dụng IEqualityComparer một cách hoàn hảo: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Câu trả lời:


173

Việc GetHashCodetriển khai của bạn luôn trả về cùng một giá trị. Distinctdựa vào một hàm băm tốt để hoạt động hiệu quả vì nó xây dựng một bảng băm bên trong .

Khi triển khai giao diện của các lớp, điều quan trọng là phải đọc tài liệu để biết bạn phải thực hiện hợp đồng nào. 1

Trong mã của bạn, giải pháp là để chuyển tiếp GetHashCodeđến Class_reglement.Numf.GetHashCodevà thực hiện nó một cách thích hợp đó.

Ngoài ra, Equalsphương pháp của bạn chứa đầy mã không cần thiết. Nó có thể được viết lại như sau (cùng ngữ nghĩa, ¼ của mã, dễ đọc hơn):

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

Cuối cùng, ToListcuộc gọi là không cần thiết và tốn thời gian: AddRangechấp nhận bất kỳ cuộc gọi nào IEnumerableđể chuyển đổi thành một Listkhông bắt buộc. cũngAsEnumerable là thừa ở đây vì dù sao việc xử lý kết quả trong cũng sẽ gây ra điều này.AddRange


1 Viết mã mà không biết nó thực sự làm gì được gọi là lập trình sùng bái hàng hóa . Đó là một thực tế phổ biến đáng ngạc nhiên. Về cơ bản nó không hoạt động.


19
Equals của bạn không thành công khi x hoặc y rỗng.
dzendras 17/12/12

4
@dzendras Tương tự cho GetHashCode. Tuy nhiên, lưu ý rằng tài liệu củaIEqualityComparer<T> không chỉ định phải làm gì với các nullđối số - nhưng các ví dụ được cung cấp trong bài viết cũng không xử lý nullđược.
Konrad Rudolph

49
Chà. Abomination là khắc nghiệt không cần thiết. Chúng tôi ở đây để giúp đỡ lẫn nhau, không xúc phạm. Tôi đoán nó làm cho một số người cười, nhưng tôi khuyên bạn nên xóa nó.
Jess

4
+1 vì đã bắt tôi đọc về "lập trình sùng bái hàng hóa" trên wiki và sau đó thay đổi dòng thẻ skype của tôi thành "// Phép thuật sâu sắc bắt đầu từ đây ... tiếp theo là một số thuật sĩ hạng nặng".
Alex

4
@NeilBenn Bạn nhầm lời khuyên thẳng thắn với sự thô lỗ. Vì OP đã chấp nhận câu trả lời (và, tôi có thể lưu ý, trong một phiên bản nghiêm khắc hơn nhiều!), Họ dường như không mắc phải sai lầm tương tự. Tôi không rõ tại sao bạn nghĩ rằng việc đưa ra lời khuyên là thô lỗ, nhưng bạn đã nhầm khi nói rằng “anh chàng không cần một bài giảng”. Tôi hoàn toàn không đồng ý: bài giảng cần thiết, và nó đã được ghi nhớ. Mã, như đã viết, là xấu và dựa trên thực hành công việc tồi. Không chỉ ra điều này sẽ là một lời khuyên và không hữu ích chút nào, vì vậy OP không thể cải thiện cách chúng hoạt động.
Konrad Rudolph

47

Hãy thử mã này:

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

Ví dụ về việc sử dụng nó sẽ là

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

19
GetHashCodecũng cần sử dụng biểu thức: return _expr.Invoke(obj).GetHashCode();Xem bài đăng này để biết cách sử dụng nó.
orad,

3

Chỉ cần mã, với việc triển khai GetHashCodeNULLxác thực:

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

Ví dụ: danh sách Class_reglement khác biệt bởi Numf

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

2

Việc bao gồm lớp so sánh của bạn (hoặc cụ thể hơn là AsEnumerable lệnh gọi bạn cần sử dụng để nó hoạt động) có nghĩa là logic sắp xếp đã chuyển từ dựa trên máy chủ cơ sở dữ liệu sang trên máy khách cơ sở dữ liệu (ứng dụng của bạn). Điều này có nghĩa là khách hàng của bạn bây giờ cần truy xuất và sau đó xử lý một số lượng lớn hơn các bản ghi, điều này sẽ luôn kém hiệu quả hơn khi thực hiện tra cứu trên cơ sở dữ liệu nơi có thể sử dụng các chỉ mục phê duyệt.

Thay vào đó, bạn nên cố gắng phát triển một mệnh đề where đáp ứng yêu cầu của bạn, hãy xem Sử dụng IEqualityComparer với mệnh đề LINQ cho các thực thể ngoại trừ để biết thêm chi tiết.


2

Nếu bạn muốn có một giải pháp chung mà không cần quyền anh:

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

sử dụng:

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

0

IEquatable<T> có thể là một cách dễ dàng hơn nhiều để thực hiện việc này với các khuôn khổ hiện đại.

Bạn nhận được một bool Equals(T other)chức năng đơn giản tuyệt vời và không có gì phải lo lắng với việc truyền hoặc tạo một lớp riêng biệt.

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

Lưu ý rằng bạn phải thực hiện GetHashCodenếu sử dụng điều này trong từ điển hoặc với một cái gì đó tương tự Distinct.

Tái bút. Tôi không nghĩ rằng bất kỳ phương thức Equals tùy chỉnh nào hoạt động với khung thực thể trực tiếp trên phía cơ sở dữ liệu (tôi nghĩ bạn biết điều này vì bạn làm AsEnumerable) nhưng đây là một phương pháp đơn giản hơn nhiều để thực hiện một Equals đơn giản cho trường hợp chung.

Nếu mọi thứ dường như không hoạt động (chẳng hạn như lỗi khóa trùng lặp khi thực hiện ToDictionary), hãy đặt một breakpoint bên trong Equals để đảm bảo rằng nó đang được nhấn và đảm bảo rằng bạn đã GetHashCodexác định (với từ khóa ghi đè).


1
Bạn vẫn cần kiểm tra null
disklosr

Tôi chưa bao giờ gặp phải điều đó nhưng tôi sẽ nhớ làm như vậy vào lần sau. Bạn có null trong Danh sách <T> hoặc tương tự không?
Simon_Weaver

1
Theo .Equals()phương pháp mà bạn dường như có so other.Hometownvới chính nó, thay vìthis.Hometown
Jake Stokes

Giáo sư. Đã sửa lỗi đánh máy :)
Simon_Weaver,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.