引用相等性和值相等性
在 C# 中,相等性分为引用相等性和值相等性。引用相等性是指,若两个引用类型的变量引用的是同一个对象,则它们具有引用相等性。
// x, y, z 都是引用类型变量 object x = new object(); object y = new object(); object z = x; // 输出 false,x 和 y 不具有引用相等性 Console.WriteLine(object.ReferenceEquals(x, y)); // 输出 true,x 和 z 具有引用相等性 Console.WriteLine(object.ReferenceEquals(x, z)); // 输出 true,x 和自身具有引用相等性 Console.WriteLine(object.ReferenceEquals(x, x));
值相等性是指,若两个变量----可以是引用类型或值类型----包含语义上相同的值,则它们具有值相等性。
int i = 5; int j = 6; int k = 5; // 输出 false,i 和 j 不具有值相等性 Console.WriteLine(i.Equals(j)); // 输出 true,i 和 k 具有值相等性 Console.WriteLine(i.Equals(k)); // 输出 true,i 和自身具有值相等性 Console.WriteLine(i.Equals(i));
== 和 != 操作符
== 操作符用于判断两个变量是否相等,默认的实现是:
- 对于字符串以外的引用类型,判断变量是否具有引用相等性
- 对于值类型,判断变量是否具有值相等性
- 对于字符串类型,判断变量是否具有值相等性
!= 操作符与 == 操作符含义相反 。
C#允许重载 == 和 != 操作符。规则是,若要重载其中的一个,则必须重载另一个。
Object.ReferenceEquals 静态方法
Object.ReferenceEquals 静态方法用于判断引用相等性,它接受两个 object 类型的参数:
int i = 5; // 输出 false,i 被装箱两次,是两个不同的对象,不具有引用相等性 Console.WriteLine(object.ReferenceEquals(i, i)); // 相当于: object x = i; object y = i; Console.WriteLine(object.ReferenceEquals(x, y));
.net 框架的源码实现中,ReferenceEquals 方法使用 == 操作符进行判断:
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Runtime.Versioning.NonVersionable] public static bool ReferenceEquals (Object objA, Object objB) { return objA == objB; }
代码参考:http://referencesource.microsoft.com/#mscorlib/system/object.cs,4d607d6d56a93c7e
字符串相等性
在 .net 中,字符串是引用类型,但 == 运算符和 Equals 方法都使用值相等性对字符串进行比较。默认的比较方式是区分大小写的,有时我们希望进行不区分大小写的比较。因此 String 类还有一个 Equals 方法的重载,允许指定如何对字符串进行相等性比较:
public bool Equals(string value, StringComparison comparisonType)
StringComparison 参数允许指定要使用的区域性、大小写和排序规则。
字符串的例子提示相等性比较不是绝对的,两个相同的对象,可能在一个上下文中相等,而在另一个上下文中不相等。
Object.Equals 实例方法
Object.Equals 实例方法的签名是:
public virtual bool Equals(Object obj)
这是一个虚方法,具体行为取决于是否进行了重写。Object 中 Equals 方法的实现是,如果参数 obj 与当前对象是同一实例,则返回 true。
ValueType 重写了 Equals 方法:若两个值类型变量的类型相同,并且所有实例字段相等,则它们具有值相等性。ValueType 中的 Equals 方法使用反射实现,对性能略有影响。对于自定义值类型,可考虑重写 Equals 方法避免反射带来的性能损失。
Object.Equals 静态方法
这是实用工具性质的方法,用于判断两个变量是否相等,方法签名是:
public static bool Equals(Object objA, Object objB)
它的实现代码如下:
public static bool Equals(Object objA, Object objB) { if (objA == objB) { return true; } if (objA == null || objB == null) { return false; } return objA.Equals(objB); }
分析:
1,若两个变量引用相同的对象,或都为 null,则返回 true。
2,若任意一个变量为 null,另一个不为 null,则返回 false。
3,若两个变量都不为 null,并且不引用同一对象,那么返回 objA.Equals(objB) 的结果。
重写 Object.Equals 实例方法
重写 Object.Equals 实例方法时,应遵循以下规则:
- 自反:x.Equals(x) 应返回 true
- 对称:x.Equals(y) 和 y.Equals(x) 应具有相同的结果
- 传递:如果 x.Equals(y) 为 true,并且 y.Equals(z) 为 true,那么 x.Equals(z) 应为 true
- 稳定:如果 x 和 y 未修改,那么连续调用 x.Equals(y) 应具有相同的结果
- 当实参为 null 时,应始终返回 null。
- 若重写 Equals 方法,那么还应重写 GetHashCode 方法,并且,两个相等对象的 GetHashCode 方法应返回相同的值
IEquatable<T> 泛型接口
IEquatable<T> 定义类型特定的相等性比较方法,此接口只定义了一个方法,签名为:
bool Equals(T other)
此接口是从 .net 2.0 开始伴随泛型集合引入的。在泛型集合上调用 Contains、IndexOf、LastIndexOf 和 Remove 等方法时,将使用此接口进行相等性比较。根据 MSDN 的说明,任何可能存储在泛型集合中的类都应实现此接口。
如果实现了 IEquatable 接口,还应重写 Object.Equals 实例方法和 Object.GetHashCode 方法。
IEqualityComparer<T> 泛型接口和 EqualityComparer<T> 泛型类
IEqualityComparer 声明了两个方法:
bool Equals(T x, T y) int GetHashCode(T obj)
此接口允许在泛型集合中跳过 T 本身的 Equals 和 GetHashCode 实现,如果向泛型集合提供了 IEqualityComparer<T> 的实例,那么集合将使用 IEqualityComparer<T> 的 Equals 和 GetHashCode 方法进行比较。
EqualityComparer<T> 为 IEqualityComparer<T> 泛型接口的实现提供基类。EqualityComparer<T>.Default 属性返回默认的相等性比较器,IEquatable<T> 与泛型集合的配合实际上是在这里起作用的:
- 若 T 实现了 IEquatable<T> 接口,则 EqualityComparer<T>.Default 使用 IEquatable<T> 接口进行相等性比较
- 否则,EqualityComparer<T>.Default 使用 Object.Equals 进行相等性比较