对象相等的机制,有所不同,取决于是引用类型还是值类型
比较引用类型的相等性
System.Object定义了3个不同的方法来比较对象的相等性:
- ReferenceEquals()
- 可以被重写的虚拟实例方法:Equals()
- 静态方法:Equals()
外加可以以下途径:
- 实现接口
IEquality<T>
- 比较运算符 ==
总共有上述5种方法。
1. ReferenceEquals()
- 是静态方法 (静态,所以不能被重写)
- 是看两个引用是否在内存中有相同的地址(或者说是看否为同一个实例)
object.ReferenceEquals(null, null)
,会返回true
private static void ReferenceEqualsSample()
{
SomeClass x = new SomeClass(), y = new SomeClass(), z = x;
bool b1 = object.ReferenceEquals(null, null); // returns true
bool b2 = object.ReferenceEquals(null, x); // returns false
bool b3 = object.ReferenceEquals(x, y); // returns false because x and y
// reference different objects
bool b4 = object.ReferenceEquals(x, z); // references the same object
}
复制代码
2. 可以被重写的虚拟实例方法:Equals()
- System.Object的这个虚拟实例方法Equals() 比较的是引用(也就是看是否为同一个实例)。
- 但是,可以在System.Object的子类(也就是任何类)里面重写这个Equals()方法,让它按某个值来比较对象实例(而不是直接比较对象实例的引用)。
这里有一个形象的使用场景示例:
-
用类的实例做字典的键 (键不能重复,所以字典的实现里会调用Equals()方法来比较键的相等性)。这时,就可以根据需要,重写Equals()方法,让作为键的实例实际上按照某个成员的值来比较相等性。
-
这里重写
Equals()
方法,需要特别注意,重写的代码不应该抛出异常。否则调用这个Equals方法的.NET基类,例如字典类就可能会出问题。
注意: 当字典的键为类实例时,相等性比较,其实也可以用重写Object.GetHashCode()的方法来做,但是和重写Equals()方法相比,重写Object.GetHashCode()工作效率比较低。
3. 静态Equals()
- Equals()的静态版本和虚实例版本作用相同
- 静态版带两个参数
它的工作原理如下:
- 两个参数,如果都是null, 则返回true;
- 有一个参数是null, 另一个不是null, 则返回false;
- 两个参数都不是null, 则调用Equals()的虚实例版本来进一步比较 (因此如果重写了Equals()的虚实例版本,也就等同于重写了Equals()的静态版本)
5. 比较运算符 ==
- 比较
运算符==
, 比较的是引用 - 但为了直观,一些类会重写这个
运算符==
,以值来做相等性比较。例如System.String
就重写了这个运算符==
,会比较字符串的内容,而不是比较引用
比较值类型的相等性
- System.ValueType类中重载了的实例方法Equals()
- 没有实际意义但存在的ReferenceEquals()
1. System.ValueType类中重载了的实例方法Equals()
- 比较的是值
- 如果是struct, sA.Equals(sB) 会挨个比较两者的所有字段,看它们的值是否相同
- 如果是struct(且字段里有引用类型),会挨个比较两者的所有字段,遇到引用类型的字段,只比较引用,这时,如果只比较引用类型字段的引用不符合需求的话,可以考虑重写这个Equals()方法,让它按照合适的值进行相等性比较。
2. 没有实际意义但存在的ReferenceEquals()
- 比较的是值类型的引用
- 但,值类型要先装箱,才能转换成引用,才能用ReferenceEquals()
- 装箱是单独装箱,这意味着会得到不同的引用
- 所以ReferenceEquals()用于值类型时,总是返回false
bool b = ReferenceEquals(v, v);
复制代码