有没有完整的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.Equals
和Object.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 == double
或double == 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的指导方针,不仅仅是因为这对我来说不是很自然,也因为超载==
没有造成任何重大的伤害。 这不像不覆盖非genericsEquals
或GetHashCode
可以反弹,因为框架不使用==
任何地方,但只有当我们自己使用它。 我从不重载==
和!=
获得的唯一真正的好处将是我无法控制的整个框架的devise的一致性。 这确实是一件大事, 所以我会坚持下去 。
通过引用语义(可变对象)
1)重写Equals
和GetHashCode
。
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)重写Equals
和GetHashCode
。
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); } }