Tôi muốn hiểu các kịch bản ở đâu IEqualityComparer<T>
và IEquatable<T>
nên được sử dụng. Tài liệu MSDN cho cả hai trông rất giống nhau.
T
triể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?
Tôi muốn hiểu các kịch bản ở đâu IEqualityComparer<T>
và IEquatable<T>
nên được sử dụng. Tài liệu MSDN cho cả hai trông rất giống nhau.
T
triể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?
Câu trả lời:
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.
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
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
T
cho 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 T
cho đẳ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 T
có 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 T
s 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 T
chí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 T
cho đẳng thức, vì T
khô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 X
và Y
trong 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.
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.
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 đề.
GetHashCode
nê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.
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
, Equals
phương thức trên một đối tượng kiểu sẽ T
cho 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 T
chí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), Contains
trên List<T>
vv tận dụng điều này. Thực hiện IEqualityComparer<T>
trên T
khô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 Person
tạ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 T
khô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>
và IEquatable<T>
chính xác tương tự Comparer<T>
và 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()
obj.Age.GetHashCode
. sẽ chỉnh sửa.
person.GetHashCode
bấ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 IEqualityComparer
là 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õ.
Age
tà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.GetHashCode
phá 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.
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.
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>