有没有完整的IEquatable实现参考?

我在这里的许多问题都涉及到IEquatable的实现。 我发现正确实施是非常困难的,因为在天真的实现中存在很多隐藏的错误,而我发现的文章相当不完整。 我想find或写一个明确的参考,其中必须包括:

  • 如何正确实现IEquatable
  • 如何正确覆盖Equals
  • 如何正确覆盖GetHashCode
  • 如何正确实现ToString方法
  • 如何正确实现运算符==
  • 如何正确执行操作符!

这样一个完整的参考已经存在?

PS:即使MSDN的参考似乎有缺陷给我

为值types实现IEquatable<T>

对值types实现IEquatable<T>与引用types有点不同。 假设我们有Implement-Your-Own-Value-Type原型,一个复数结构。

 public struct Complex { public double RealPart { get; set; } public double ImaginaryPart { get; set; } } 

我们的第一步是实现IEquatable<T>并重写Object.EqualsObject.GetHashCode

 public bool Equals(Complex other) { // Complex is a value type, thus we don't have to check for null // if (other == null) return false; return (this.RealPart == other.RealPart) && (this.ImaginaryPart == other.ImaginaryPart); } public override bool Equals(object other) { // other could be a reference type, the is operator will return false if null if (other is Complex) return this.Equals((Complex)other); else return false; } public override int GetHashCode() { return this.RealPart.GetHashCode() ^ this.ImaginaryPart.GetHashCode(); } 

除了运营商之外,我们只需很less的努力就能得到正确的实施。 添加运算符也是一个微不足道的过程:

 public static bool operator ==(Complex term1, Complex term2) { return term1.Equals(term2); } public static bool operator !=(Complex term1, Complex term2) { return !term1.Equals(term2); } 

一个精明的读者会注意到,我们可能应该实现IEquatable<double>因为Complex可以与底层的值types互换。

 public bool Equals(double otherReal) { return (this.RealPart == otherReal) && (this.ImaginaryPart == 0.0); } public override bool Equals(object other) { // other could be a reference type, thus we check for null if (other == null) return base.Equals(other); if (other is Complex) { return this.Equals((Complex)other); } else if (other is double) { return this.Equals((double)other); } else { return false; } } 

如果我们添加IEquatable<double> ,我们需要四个运算符,因为您可以具有Complex == doubledouble == Complex (并且对于operator != )相同:

 public static bool operator ==(Complex term1, double term2) { return term1.Equals(term2); } public static bool operator ==(double term1, Complex term2) { return term2.Equals(term1); } public static bool operator !=(Complex term1, double term2) { return !term1.Equals(term2); } public static bool operator !=(double term1, Complex term2) { return !term2.Equals(term1); } 

所以你有它,只需要很less的努力,我们有一个正确和有用的实现IEquatable<T>的值types:

 public struct Complex : IEquatable<Complex>, IEquatable<double> { } 

我相信,像.NET的devise那样简单得像检查对象是否平等一样简单。

对于Struct

1)实现IEquatable<T> 。 它显着提高了性能。

2)既然你现在有自己的Equals ,重写GetHashCode ,并与各种相等检查覆盖object.Equals一致。

3)重载==!=运算符不需要虔诚地完成,因为编译器会警告你是否无意中将一个结构与另一个结构等同为==!= ,但是为了与Equals方法保持一致,这是很好的做法。

 public struct Entity : IEquatable<Entity> { public bool Equals(Entity other) { throw new NotImplementedException("Your equality check here..."); } public override bool Equals(object obj) { if (obj == null || !(obj is Entity)) return false; return Equals((Entity)obj); } public static bool operator ==(Entity e1, Entity e2) { return e1.Equals(e2); } public static bool operator !=(Entity e1, Entity e2) { return !(e1 == e2); } public override int GetHashCode() { throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here..."); } } 

上课

从MS:

大多数引用types不应该重载相等运算符,即使它们覆盖了Equals。

对我来说==感觉像价值的平等,更像是一个Equals方法的语法糖。 写a == b比写a.Equals(b)更直观。 很less我们需要检查参考平等。 在处理物理对象的逻辑表示的抽象层次中,这不是我们需要检查的东西。 我认为==Equals有不同的语义,实际上可能会让人困惑。 我相信它应该是==为价值平等和平等参考(或更好的名字像IsSameAs )平等首先。 我不想在这里认真对待MS的指导方针,不仅仅是因为这对我来说不是很自然,也因为超载==没有造成任何重大的伤害。 这不像不覆盖非genericsEqualsGetHashCode可以反弹,因为框架不使用==任何地方,但只有当我们自己使用它。 我从不重载==!=获得的唯一真正的好处将是我无法控制的整个框架的devise的一致性。 这确实是一件大事, 所以我会坚持下去

通过引用语义(可变对象)

1)重写EqualsGetHashCode

2)实现IEquatable<T>不是必须的,但如果你有一个很好。

 public class Entity : IEquatable<Entity> { public bool Equals(Entity other) { if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(null, other)) return false; //if your below implementation will involve objects of derived classes, then do a //GetType == other.GetType comparison throw new NotImplementedException("Your equality check here..."); } public override bool Equals(object obj) { return Equals(obj as Entity); } public override int GetHashCode() { throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here..."); } } 

使用值语义(不可变对象)

这是棘手的部分。 如果不小心,可以轻松搞定

1)重写EqualsGetHashCode

2)重载==!=匹配Equals确保它适用于空值

2)实现IEquatable<T>不是必须的,但如果你有一个很好。

 public class Entity : IEquatable<Entity> { public bool Equals(Entity other) { if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(null, other)) return false; //if your below implementation will involve objects of derived classes, then do a //GetType == other.GetType comparison throw new NotImplementedException("Your equality check here..."); } public override bool Equals(object obj) { return Equals(obj as Entity); } public static bool operator ==(Entity e1, Entity e2) { if (ReferenceEquals(e1, null)) return ReferenceEquals(e2, null); return e1.Equals(e2); } public static bool operator !=(Entity e1, Entity e2) { return !(e1 == e2); } public override int GetHashCode() { throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here..."); } } 

请特别注意,如果您的类可以被inheritance,应该如何处理,在这种情况下,您将必须确定基类对象是否可以等于派生类对象。 理想情况下,如果没有派生类的对象用于相等性检查,那么基类实例可以等于派生类实例,在这种情况下,不需要在基类的generics等式中检查Type相等。

一般来说不要重复代码。 我可以做一个通用的抽象基类( IEqualizable<T>左右)作为模板,以便重新使用更容易,但令人遗憾的是在C#阻止我从其他类派生。

在阅读MSDN时,我相当确定正确实现的最好的例子是在IEquatable.Equals方法页面。 我唯一的偏差是以下几点:

 public override bool Equals(Object obj) { if (obj == null) return base.Equals(obj); if (! (obj is Person)) return false; // Instead of throw new InvalidOperationException else return Equals(obj as Person); } 

对于那些想知道偏差的人来说,它来源于Object.Equals(Object) MSDN页面:

Equals的实现不能抛出exception。

我发现另一个参考,它是.NET匿名types实现。 对于具有int和double属性的匿名types,我反汇编了以下C#代码:

 public class f__AnonymousType0 { // Fields public int A { get; } public double B { get; } // Methods public override bool Equals(object value) { var type = value as f__AnonymousType0; return (((type != null) && EqualityComparer<int>.Default.Equals(this.A, type.A)) && EqualityComparer<double>.Default.Equals(this.B, type.B)); } public override int GetHashCode() { int num = -1134271262; num = (-1521134295 * num) + EqualityComparer<int>.Default.GetHashCode(this.A); return ((-1521134295 * num) + EqualityComparer<double>.Default.GetHashCode(this.B); } public override string ToString() { StringBuilder builder = new StringBuilder(); builder.Append("{ A = "); builder.Append(this.A); builder.Append(", B = "); builder.Append(this.B); builder.Append(" }"); return builder.ToString(); } } 

我只需要从这个类中派生出来

 public abstract class DataClass : IEquatable<DataClass> { public override bool Equals(object obj) { var other = obj as DataClass; return this.Equals(other); } public bool Equals(DataClass other) { return (!ReferenceEquals(null, other)) && this.Execute((self2, other2) => other2.Execute((other3, self3) => self3.Equals(other3), self2) , other); } public override int GetHashCode() { return this.Execute(obj => obj.GetHashCode()); } public override string ToString() { return this.Execute(obj => obj.ToString()); } private TOutput Execute<TOutput>(Func<object, TOutput> function) { return this.Execute((obj, other) => function(obj), new object()); } protected abstract TOutput Execute<TParameter, TOutput>( Func<object, TParameter, TOutput> function, TParameter other); } 

然后像这样实现抽象的方法

 public class Complex : DataClass { public double Real { get; set; } public double Imaginary { get; set; } protected override TOutput Execute<TParameter, TOutput>( Func<object, TParameter, TOutput> function, TParameter other) { return function(new { Real = this.Real, Imaginary = this.Imaginary, }, other); } }