这个编程练习展示了 C# 中相等性的若干性质:
Object.Equals()==和!=测试相等性。这被认为比使用这些类型同样提供的 Equals() 方法更符合惯用法。Java 程序员在处理字符串时应当留意:==在 C# 中按值比较,而回到 Java 后按引用比较。object继承来的Equals()方法进行比较。如果你做相等性测试的目的是确认两个对象是同一个实例,那么依赖object的实现就足够了。如果不是,就需要重写object.Equals()。Equals()。Equals()会对简单类型的成员用==做相等性测试,对引用类型的成员则递归调用Equals()。class StatusBar
{
private readonly int width = 200, height = 20;
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return width == (other as StatusBar).width && height == (other as StatusBar).height;
}
}
class Window
{
private readonly string title = "Main";
private readonly StatusBar statusBar = new StatusBar();
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
}
}
object.ReferenceEquals()用于比较两个对象,判断它们是不是同一个实例。这样写更清晰,而且在Equals()和==已被重写或重载的情况下也是必需的。var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true
public override bool Equals(object obj)之外,IDE 通常还会生成重载protected bool Equals(FacialFeatures other),供涉及继承时使用。派生类可以调用基类的Equals(),然后再加上自己的测试。==运算符以及Equals()方法(参见operator-overloading练习),或者你只关心引用是否相等,否则不要使用==。structs中的相等性测试在structs练习中讨论。tuples的相等性在tuples练习中讨论。protected bool Equals(T other)
{
return field1 == other.field1 && field2.Equals(other.field2);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((T) obj);
}
SequenceEquals(),但没有 LINQ 时,就只能遍历两个集合并逐个比较元素。==和!=,参见operator-overloading练习。Object.GetHashCode()object.GetHashCode()返回一个 32 位整数形式的哈希码。字典和集合类(比如Dictionary<T>和HashSet<T>)用哈希码来高效地存储和检索对象。对于字典来说,哈希针对的是键。Equals(),也应该重写GetHashCode()。Equals()和GetHashCode()之间存在一种关系,字典、哈希集以及任何使用哈希码的类要想行为正确,这种关系就必须成立。你应该正确地实现这个方法,以免给日后可能添加基于哈希码的集合的维护者埋下陷阱。Equal()返回 true),那么这两个对象的GetHashCode()必须返回相同的值。反过来则不成立。这种关系并不对称。想象一个查找函数:它先根据哈希码找到一个“桶”,然后用相等性测试挑出具体的项。HashCode.Combine(),传入相等性测试中用到的值(或其中一部分)。请记住,传给Combine()的信息越多,哈希实现很可能就越高效。public class Assessment
{
private int rating;
private Person boss;
public override int GetHashCode()
{
return HashCode.Combine(rating, boss);
}
}
要想略微提升性能,尤其是在对象属于集合的情况下,可以添加一个重载成员public bool Equals(T other)。
这样对引用类型可以省去一定量的类型检查,对值类型则可以省去一次装箱,因为值类型不必再被转换成对象(装箱)作为public override bool Equals(object other)的实参。
如果你的类添加了IEquatable<T>接口,就必须实现这个重载。除非你的代码里有接收IEquatable<T>类型对象的例程(这些例程大概会做一些与相等性有关的有意思的事情,而且不关心实现类是什么),否则其实没有别的令人信服的理由要包含这个接口。
IEqualityComparer<T>如果一个类可以用两种不同的方式唯一标识,比如一个Person类既有 SSID 又有唯一的电子邮箱地址,那么 .NET 提供了一种办法,让两个不同的集合使用不同的哈希码和相等性测试。每个集合都可以有IEqualityComparer<T>的不同实现,各自带有自己的Equals()和GetHashCode()方法。你可以有一个以 SSID 为键的字典,另一个以电子邮箱地址为键的字典。
在使用IEqualityComparer<T>时,通常仍要在你的条目类上重写Equals()和GetHashCode(),以避免集合类之外出现问题。
使用IEqualityComparer<T>时需要考虑的一点是,私有方法等不会对相等性测试可用。
如果只涉及一个哈希集合,那么最好避免使用IEqualityComparer<T>,而把相等性和哈希码封装在对象自身之中。对象与哈希集合之间的这种关键依赖无法由编译器强制保证,这本身就不理想;但如果把哈希和比较逻辑放到别处,维护者就更容易在修改类的成员时不顾及对集合的影响。
对粗心的程序员来说,一个可能带来挑战的基元类型就是测试浮点数值的相等性。这一点在floating-point-numbers概念的 about.md 文档中讨论。
这篇文章展示了在相等性和继承方面必须做出的一些决定。只有当你同时操作某个类及其派生类的实例、并且需要测试两者之间的相等性时,这些问题才会与你有关。