Sự khác biệt giữa IEqualityComparer <T> và IEquitable <T> là gì?


151

Tôi muốn hiểu các kịch bản ở đâu IEqualityComparer<T>IEquatable<T>nên được sử dụng. Tài liệu MSDN cho cả hai trông rất giống nhau.


1
MSDN sez: "Giao diện này cho phép thực hiện so sánh đẳng thức tùy chỉnh cho các bộ sưu tập. " Tất nhiên được suy ra trong câu trả lời đã chọn. MSDN cũng khuyên bạn nên kế thừa EqualityComparer<T>thay vì triển khai giao diện "bởi vì EqualityComparer<T>kiểm tra sự bằng nhau bằng cách sử dụngIEquatable<T>
radarbob

... ở trên gợi ý tôi nên tạo một bộ sưu tập tùy chỉnh cho bất kỳ Ttriển khai nào IEquatable<T>. Một bộ sưu tập như List<T>có một số loại lỗi tinh tế trong đó nếu không?
radarbob


@RamilShavaleev liên kết bị hỏng
ChessMax

Câu trả lời:


117

IEqualityComparer<T>là một giao diện cho một đối tượng thực hiện so sánh trên hai đối tượng cùng loại T.

IEquatable<T>dành cho một đối tượng thuộc loại Tđể nó có thể tự so sánh với một đối tượng khác cùng loại.


1
Sự khác biệt tương tự là giữa IComparable / IComparer
boctulus 19/1/2015

3
Đội trưởng Rõ ràng
Stepan Ivanenko

(Đã chỉnh sửa) IEqualityComparer<T>là giao diện cho một đối tượng (thường là một lớp nhẹ khác với T) cung cấp các chức năng so sánh hoạt động trênT
rwong

61

Khi quyết định nên sử dụng IEquatable<T>hay IEqualityComparer<T>, người ta có thể hỏi:

Có một cách ưa thích để kiểm tra hai trường hợp Tcho sự bình đẳng, hoặc có một số cách hợp lệ như nhau?

  • Nếu chỉ có một cách để kiểm tra hai trường hợp Tcho đẳng thức hoặc nếu một trong một số phương thức được ưu tiên, thì đó IEquatable<T>sẽ là lựa chọn đúng: Giao diện này được cho là chỉ được thực hiện T, để một trường hợp Tcó kiến ​​thức nội bộ về làm thế nào để so sánh chính nó với một ví dụ khác của T.

  • Mặt khác, nếu có một số phương pháp hợp lý như nhau để so sánh hai Ts cho đẳng thức, IEqualityComparer<T>có vẻ phù hợp hơn: Giao diện này không có nghĩa là được thực hiện bởi Tchính nó, mà bởi các lớp "bên ngoài" khác. Do đó, khi kiểm tra hai trường hợp Tcho đẳng thức, vì Tkhông có hiểu biết bên trong về đẳng thức, bạn sẽ phải đưa ra lựa chọn rõ ràng về một IEqualityComparer<T>trường hợp thực hiện kiểm tra theo yêu cầu cụ thể của bạn.

Thí dụ:

Hãy xem xét hai loại này (được cho là có ngữ nghĩa giá trị ):

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

Tại sao chỉ một trong những loại này kế thừa IEquatable<>, nhưng không phải là loại khác?

Về lý thuyết, chỉ có một cách hợp lý để so sánh hai trường hợp của một trong hai loại: Chúng bằng nhau nếu các thuộc tính XYtrong cả hai trường hợp bằng nhau. Theo suy nghĩ này, cả hai loại nên thực hiện IEquatable<>, bởi vì dường như không có cách nào khác để thực hiện một bài kiểm tra bình đẳng.

Vấn đề ở đây là việc so sánh các số dấu phẩy động cho đẳng thức có thể không hoạt động như mong đợi, do các lỗi làm tròn số phút . Có nhiều phương pháp khác nhau để so sánh các số dấu phẩy động cho gần bằng nhau, mỗi phương pháp đều có những lợi thế và sự đánh đổi cụ thể và bạn có thể muốn chọn cho mình phương pháp nào phù hợp.

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

Lưu ý rằng trang tôi liên kết đến (ở trên) nói rõ rằng thử nghiệm này cho gần bằng có một số điểm yếu. Vì đây là một IEqualityComparer<T>triển khai, bạn chỉ có thể trao đổi nó nếu nó không đủ tốt cho mục đích của bạn.


6
Phương pháp so sánh được đề xuất để kiểm tra đẳng thức hai điểm bị phá vỡ, vì bất kỳ IEqualityComparer<T>triển khai hợp pháp nào cũng phải thực hiện mối quan hệ tương đương, hàm ý rằng trong mọi trường hợp hai đối tượng so sánh bằng một phần ba, chúng phải so sánh bằng nhau. Bất kỳ lớp nào thực hiện IEquatable<T>hoặc IEqualityComparer<T>theo cách trái ngược với ở trên đều bị hỏng.
supercat

5
Một ví dụ tốt hơn sẽ là so sánh giữa các chuỗi. Thật hợp lý khi có một phương thức so sánh chuỗi chỉ coi các chuỗi là bằng nhau nếu chúng chứa cùng một chuỗi byte, nhưng có các phương thức so sánh hữu ích khác cũng tạo thành quan hệ tương đương , chẳng hạn như so sánh không phân biệt chữ hoa chữ thường. Lưu ý rằng một IEqualityComparer<String>từ coi "xin chào" bằng cả "Xin chào" và "hElLo" phải coi "Xin chào" và "hElLo" bằng nhau, nhưng đối với hầu hết các phương pháp so sánh sẽ không thành vấn đề.
supercat

@supercat, cảm ơn những phản hồi có giá trị. Có vẻ như tôi sẽ không được sửa đổi câu trả lời của mình trong vài ngày tới, vì vậy nếu bạn muốn, xin vui lòng chỉnh sửa khi bạn thấy phù hợp.
stakx - không còn đóng góp vào

Đây chính xác là những gì tôi cần và những gì tôi đang làm. Tuy nhiên, cần phải ghi đè GetHashCode, trong đó nó sẽ trả về false một khi các giá trị khác nhau. Làm thế nào để bạn xử lý GetHashCode của bạn?
teapeng

@teapeng: Không chắc bạn đang nói về trường hợp sử dụng cụ thể nào. Nói chung, GetHashCodenên cho phép bạn nhanh chóng xác định xem hai giá trị có khác nhau không. Các quy tắc đại khái như sau: (1) GetHashCode phải luôn tạo cùng một mã băm cho cùng một giá trị. (2) GetHashCode nên nhanh (nhanh hơn Equals). (3) GetHashCode không cần phải chính xác (không chính xác như Equals). Điều này có nghĩa là nó có thể tạo ra cùng một mã băm cho các giá trị khác nhau. Càng chính xác bạn có thể làm cho nó tốt hơn, nhưng có lẽ điều quan trọng hơn là giữ cho nó nhanh.
stakx - không còn đóng góp

27

Bạn đã có định nghĩa cơ bản về những gì họ đang có . Nói tóm lại, nếu bạn triển khai IEquatable<T>trên lớp T, Equalsphương thức trên một đối tượng kiểu sẽ Tcho bạn biết nếu chính đối tượng đó (đối tượng được kiểm tra tính bằng) có bằng với một thể hiện khác cùng loại hay không T. Trong khi đó, IEqualityComparer<T>là để kiểm tra sự bằng nhau của bất kỳ hai trường hợp nào T, thường nằm ngoài phạm vi của các thể hiện của T.

Lúc đầu họ có thể gây nhầm lẫn. Từ định nghĩa, rõ ràng là do đó IEquatable<T>(được định nghĩa trong Tchính lớp ) phải là tiêu chuẩn thực tế để thể hiện tính duy nhất của các đối tượng / thể hiện của nó. HashSet<T>, Dictionary<T, U>(Xem xét GetHashCodeđược ghi đè cũng), Containstrên List<T>vv tận dụng điều này. Thực hiện IEqualityComparer<T>trên Tkhông giúp các trường hợp chung nêu trên. Sau đó, có rất ít giá trị để thực hiện IEquatable<T>trên bất kỳ lớp nào khác ngoài T. Điều này:

class MyClass : IEquatable<T>

hiếm khi có ý nghĩa.

Mặt khác

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

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

là nó nên được thực hiện như thế nào

IEqualityComparer<T>có thể hữu ích khi bạn yêu cầu xác thực tùy chỉnh bình đẳng, nhưng không phải là quy tắc chung. Chẳng hạn, trong một lớp học Persontại một thời điểm nào đó, bạn có thể yêu cầu kiểm tra sự bình đẳng của hai người dựa trên tuổi của họ. Trong trường hợp đó bạn có thể làm:

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

Để kiểm tra chúng, hãy thử

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

Tương tự như vậy IEqualityComparer<T>trên Tkhông có ý nghĩa.

class Person : IEqualityComparer<Person>

Đúng là nó hoạt động, nhưng trông không đẹp mắt và đánh bại logic.

Thông thường những gì bạn cần là IEquatable<T>. Ngoài ra lý tưởng bạn có thể chỉ có một IEquatable<T>trong khi nhiều IEqualityComparer<T>có thể dựa trên các tiêu chí khác nhau.

Các IEqualityComparer<T>IEquatable<T>chính xác tương tự Comparer<T>IComparable<T>được sử dụng cho mục đích so sánh chứ không phải tương đương; một chủ đề tốt ở đây nơi tôi đã viết cùng một câu trả lời :)


public int GetHashCode(Person obj)nên trở lạiobj.GetHashCode()
hyankov

@HristoYankov nên trở về obj.Age.GetHashCode. sẽ chỉnh sửa.
nawfal

Hãy giải thích tại sao trình so sánh phải đưa ra một giả định như vậy về những gì Hash của đối tượng mà nó so sánh? Trình so sánh của bạn không biết Người tính toán Hash của mình như thế nào, tại sao bạn lại ghi đè lên điều đó?
hyankov

@HristoYankov Trình so sánh của bạn không biết cách tính toán Hash của nó - Chắc chắn, đó là lý do tại sao chúng tôi không trực tiếp gọi person.GetHashCodebất cứ nơi nào .... tại sao bạn lại ghi đè lên điều đó? - Chúng tôi ghi đè vì toàn bộ quan điểm IEqualityComparerlà có một triển khai so sánh khác theo quy tắc của chúng tôi - các quy tắc chúng tôi thực sự biết rõ.
nawfal

Trong ví dụ của tôi, tôi cần căn cứ so sánh Agetài sản của mình, vì vậy tôi gọi Age.GetHashCode. Tuổi là loại int, nhưng bất kể loại nào là hợp đồng mã băm trong .NET là thế different objects can have equal hash but equal objects cant ever have different hashes. Đây là một hợp đồng chúng ta có thể tin tưởng một cách mù quáng. Chắc chắn các so sánh tùy chỉnh của chúng tôi cũng phải tuân thủ quy tắc này. Nếu bao giờ gọi someObject.GetHashCodephá vỡ mọi thứ, thì đó là vấn đề với những người thực hiện loại someObject, đó không phải là vấn đề của chúng tôi.
nawfal

11

IEqualityComparer được sử dụng khi sự bình đẳng của hai đối tượng được triển khai bên ngoài, ví dụ: nếu bạn muốn xác định một trình so sánh cho hai loại mà bạn không có nguồn hoặc trong trường hợp sự bình đẳng giữa hai điều chỉ có ý nghĩa trong một số bối cảnh hạn chế.

IEquitable dành cho chính đối tượng (đối tượng được so sánh về đẳng thức) để thực hiện.


Bạn là người duy nhất thực sự nêu ra vấn đề "Cắm bình đẳng" (sử dụng bên ngoài). cộng 1.
Royi Namir

4

Một so sánh hai Ts. Cái khác có thể so sánh nó với Ts khác . Thông thường, bạn sẽ chỉ cần sử dụng một lúc, không phải cả hai.

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.