System.Object提供了名为Equals的虚方法,作用是在两个对象包含相同值的前提下返回true,内部实现
public class Object { public virtual Boolean Equals(Object obj) { //比较两个引用指向同一个对象,他们肯定包含相同的值 if (this == obj) return true; //假定对象不包含相同的值 return false; } }
乍一看,这似乎就是Euqals的合理实现,假如this和obj实参引用同一个对象,就返回true,似乎合理是因为Equals知道对象
肯定包含和它一样的值,但假如实参引用不同对象,Equals就不肯定对象是否包含相同的值,所以返回false,换言之,
对于Object的Equals方法的默认实现:它实现的实际是同一性,而非相等性。
所以理想的实现应该是下面这样:
public class Object { public virtual Boolean Equals(object obj) { //要比较的对象不能为空 if (obj == null) return false; //如果对象属于不同类型,则肯定不相等 if (this.GetType() != obj.GetType()) return false; //如果对象属于相同的类型,那么在它们所有字段都匹配的前提下返回true //由于System.Object没有定义任何字段,所有字段是匹配的 return true; } }
类型重写Equals方法时应调用其基类的Equals实现(除非基类就是Object),另外,由于类型能够重写Object的Euqals方法,
所以不能再用它来测试同一性。
为了解决这个问题,Object 提供了静态方法ReferenceEquals,其原型如下:
public class Object { pubic static Boolean ReferenceEquals(Object objA, Object objB) { return (objA == objB) } }
检查同一性(看两个引用是否指向同一个对象)务必调用ReferenceEquals
不应该使用C#的==操作符(除非先把这两个操作数都转型为Object),因为某个操作数的类型可能重载了==操作符,
为其赋予不同于“同一性”的语义。
可以看到,在涉及对象相等性和同一性的时候,.NET Framework 的设计很容易使人混淆。
System.ValueType(所有值类型的基类)就重写了Object的Equals方法,并进行了正确的实现来执行值的相等性检查(而不是同一性检查)。
内部实现:
[SecuritySafeCritical] [__DynamicallyInvokable] public override bool Equals(object obj) { if (obj == null) return false; RuntimeType type = (RuntimeType) this.GetType(); if ((RuntimeType) obj.GetType() != type) return false; object a = (object) this; if (ValueType.CanCompareBits((object) this)) return ValueType.FastEqualsCheck(a, obj); FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
//比较类型的每个实例字段,任何字段不相等,都返回false for (int index = 0; index < fields.Length; ++index) { object obj1 = ((RtFieldInfo) fields[index]).UnsafeGetValue(a); object obj2 = ((RtFieldInfo) fields[index]).UnsafeGetValue(obj); if (obj1 == null) { if (obj2 != null) return false; } else if (!obj1.Equals(obj2)) return false; } return true; }
可以看到,ValueType的Equals方法利用反射,由于反射机制慢的原因,应该定义自己的值类型时重写Equals方法来提供
自己的实现,从而提高值类型相等性比较的性能。重写Equals时,可能还需要实现一下方法:
让类型实现System.IEquatable<T>接口的Equals方法
这个泛型接口允许定义类型安全的Equals方法
重载==和!=操作符方法
通常应实现这些操作符方法,在内部调用类型安全的Equals
如果出于排序的目的而比较类型的实例,类型还应实现System.IComparable的CompareTo方法和System.IComparable<T>类型
安全的CompareTo,如果实现了这些方法,还可考虑重载各种比较操作符方法(<,<=,>,>=),这些方法内部调用类型安全的CompareTo。