实施IEquatable<T> https://msdn.microsoft.com/en-us/library/ms131187%28v=vs.110%29.aspx(通常与覆盖继承的Object.Equals https://msdn.microsoft.com/en-us/library/bsc2ak47%28v=vs.110%29.aspx and Object.GetHashCode https://msdn.microsoft.com/en-us/library/system.object_methods%28v=vs.110%29.aspx方法)在所有自定义类型上。对于复合类型,调用包含的类型Equals
包含类型中的方法。对于包含的集合,请使用SequenceEqual https://msdn.microsoft.com/en-us/library/bb348567.aspx扩展方法,内部调用IEquatable<T>.Equals
or Object.Equals
在每个元素上。这种方法显然需要您扩展类型的定义,但其结果比任何涉及序列化的通用解决方案都要快。
Edit:这是一个具有三层嵌套的人为示例。
对于值类型,您通常可以直接调用它们Equals
方法。即使从未显式分配字段或属性,它们仍然具有默认值。
对于引用类型,您应该首先调用ReferenceEquals https://msdn.microsoft.com/en-us/library/system.object.referenceequals%28v=vs.110%29.aspx,它检查引用相等性——当您碰巧引用同一个对象时,这将提高效率。它还可以处理两个引用都为空的情况。如果该检查失败,请确认您的实例的字段或属性不为空(以避免NullReferenceException
)并调用它的Equals
方法。由于我们的成员类型正确,IEquatable<T>.Equals
方法被直接调用,绕过重写Object.Equals
方法(由于类型转换,其执行速度会稍微慢一些)。
当你覆盖Object.Equals
,您还需要覆盖Object.GetHashCode
;为了简洁起见,我在下面没有这样做。
public class Person : IEquatable<Person>
{
public int Age { get; set; }
public string FirstName { get; set; }
public Address Address { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Person);
}
public bool Equals(Person other)
{
if (other == null)
return false;
return this.Age.Equals(other.Age) &&
(
object.ReferenceEquals(this.FirstName, other.FirstName) ||
this.FirstName != null &&
this.FirstName.Equals(other.FirstName)
) &&
(
object.ReferenceEquals(this.Address, other.Address) ||
this.Address != null &&
this.Address.Equals(other.Address)
);
}
}
public class Address : IEquatable<Address>
{
public int HouseNo { get; set; }
public string Street { get; set; }
public City City { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Address);
}
public bool Equals(Address other)
{
if (other == null)
return false;
return this.HouseNo.Equals(other.HouseNo) &&
(
object.ReferenceEquals(this.Street, other.Street) ||
this.Street != null &&
this.Street.Equals(other.Street)
) &&
(
object.ReferenceEquals(this.City, other.City) ||
this.City != null &&
this.City.Equals(other.City)
);
}
}
public class City : IEquatable<City>
{
public string Name { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as City);
}
public bool Equals(City other)
{
if (other == null)
return false;
return
object.ReferenceEquals(this.Name, other.Name) ||
this.Name != null &&
this.Name.Equals(other.Name);
}
}
Update: 这个答案是几年前写的。从那时起,我开始放弃实施IEquality<T>
对于此类场景的可变类型。平等有两个概念:identity and 等价。在内存表示级别,它们通常被区分为“引用相等”和“值相等”(参见平等比较 https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/equality-comparisons)。然而,同样的区别也适用于域级别。假设你的Person
类有一个PersonId
财产,对于现实世界中每个不同的人来说都是独一无二的。两个物体是否应该具有相同的PersonId
但不同Age
值被认为是相同还是不同?上面的答案假设一个是等价的。然而,它的用途有很多IEquality<T>
接口,例如集合,假设此类实现提供了identity。例如,如果您要填充HashSet<T>
,您通常会期望TryGetValue(T,T) https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1.trygetvalue调用返回仅共享参数标识的现有元素,不一定是内容完全相同的等效元素。这个概念是由注释强制执行的GetHashCode https://learn.microsoft.com/en-us/dotnet/api/system.object.gethashcode#notes-to-inheritors:
一般来说,对于可变引用类型,您应该覆盖GetHashCode()
除非:
- 您可以从不可变的字段计算哈希码;或者
- 当可变对象包含在依赖于其哈希码的集合中时,您可以确保该对象的哈希码不会更改。