什么是相等
在C#中我们经常会需要判断两个变量是否相等,相等理论上有两种:
- 同一性(identity),即是否两个变量是否指向同一个对象。
- 相等性(equality),即两个变量内部的值是否相同,例如两个字符串的内容是否相同。
显然如果两个变量是相同对象,那么它们也必然相等。根据具体使用的上下文我们可能需要判断同一性或者相等性之一,因此就需要了解如何进行判断。
C#判断相等的方式
C#的System.Object类提供了如下方式来判断对象的相等性:
public static bool Equals(Object? objA, Object? objB);
public static bool ReferenceEquals(Object? objA, Object? objB);
public virtual bool Equals(Object? obj);
operator ==;
首先是ReferenceEquals,这个顾名思义就是判断两个变量是否引用同一个对象,在需要明确判断引用相等的时候可以使用这个函数。
其次是Equals方法,这个方法在Object类中定义,默认实现是调用ReferenceEquals,即判断引用是否相等。但是我们看到这是一个虚方法,因此很多类型重写了这个方法,例如String类型,重写后比较的是字符串的内容是否相同。
而相等运算符和Equals方法类似,也可以被重载,以实现自定义类型的相等判断。根据官方文档介绍,对于值类型是判断其内部值是否相同,对于引用类型是默认判断是否引用同一对象,对于委托和字符串等也有各自的实现。
值得注意的是,用户定义的struct类型默认没有支持==操作符,但是可以使用ValueType的Equals方法来判断两个值类型的内部值是否相同,例如:
static void Main(string[] args)
{
Point p1 = new Point(1, 1);
Point p2 = new Point(1, 1);
Console.WriteLine("p1 == p2:{0}", p1.Equals(p2)); // 输出 p1 == p2:True
Console.WriteLine("p1 == p2:{0}", p1 == p2); // 编译错误 Operator '==' cannot be applied to operands of type 'Point' and 'Point'
}
internal struct Point
{
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
}
}
综上,C#提供了多种方式来判断对象的相等性,包括引用相等和值相等,开发者需要根据具体需求选择合适的方法进行判断。
如何自定义相等判断
我们自定义的类如果需要实现自己的相等判断逻辑,那么可以重载Object的public virtual bool Equals(Object? obj)
方法以及重写==操作运算符,如果重写了其中之一,那么推荐也重写另一个相等判断,否则可能由于使用习惯使用了没有重写的相等判断造成bug。
重写时需要注意相等性判断要符合一下特性:
- 自反性。x.Equals(x) 应该返回 true
- 对称性。x.Equals(y) 应该返回和 y.Equals(x) 相同的结果
- 可传递。x.Equals(y) 和 y.Equals(z) 都返回 true,那么 x.Equals(z) 也应该返回 true
- 一致性。x.Equals(y) 多次调用应该返回相同的结果,除非x或y的值发生了变化。
如果重写了public virtual bool Equals(Object? obj)
方法,那么也应该重写GetHashCode
方法,因为相等的对象必须有相同的哈希码,否则以这个类的对象作为key来索引时会导致相同的对象索引到不同的数据,而且编译器也会给出有一个编译警告。
除了使用继承来的Object的相关方法来判断相等,我们还可以继承IEquatable