Many of my questions here on SO concerns IEquatable implementation. I found it being extremely difficult to implement correctly, because there are many hidden bugs in the naïve implementation, and the articles I found about it are quite incomplete. I want to find or write a definitive reference which must include:
我在这里提出的许多问题涉及IEquatable的实现。我发现正确实现起来非常困难,因为在天真的实现中存在许多隐藏的错误,我发现的关于它的文章非常不完整。我想找到或写出一个必须包括的权威性参考:
- How to implement IEquatable correctly
- 如何正确实现IEquatable
- How to override Equals correctly
- 如何正确覆盖Equals
- How to override GetHashCode correctly
- 如何正确覆盖GetHashCode
- How to implement the ToString method correctly
- 如何正确实现ToString方法
- How to implement the operator == correctly
- 如何正确实现operator ==
- How to implement the operator != correctly
- 如何实现运算符!=正确
Such a complete reference already exists?
这样一个完整的参考已经存在?
PS: Even MSDN reference seems flawed to me
PS:即使是MSDN引用对我来说也是有缺陷的
5 个解决方案
#1
21
Implementing IEquatable<T>
for a Value Type
Implementing IEquatable<T>
for a value type is a little bit different than for a reference type. Let's assume we have the Implement-Your-Own-Value-Type archetype, a Complex number struct.
为值类型实现IEquatable
public struct Complex
{
public double RealPart { get; set; }
public double ImaginaryPart { get; set; }
}
Our first step would be to implement IEquatable<T>
and override Object.Equals
and Object.GetHashCode
:
我们的第一步是实现IEquatable
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();
}
With very little effort we have a correct implementation, excepting the operators. Adding the operators is also a trivial process:
除了操作员之外,我们只需要很少的努力就能得到正确的实施。添加运算符也是一个简单的过程:
public static bool operator ==(Complex term1, Complex term2)
{
return term1.Equals(term2);
}
public static bool operator !=(Complex term1, Complex term2)
{
return !term1.Equals(term2);
}
An astute reader would notice that we should probably implement IEquatable<double>
since Complex
numbers could be interchangeable with the underlying value type.
精明的读者会注意到我们应该实现IEquatable
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;
}
}
We need four operators if we add IEquatable<double>
, because you can have Complex == double
or double == Complex
(and the same for operator !=
):
如果我们添加IEquatable
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);
}
So there you have it, with minimal effort we have a correct and useful implementation IEquatable<T>
for a value type:
所以你有了它,只需要很少的努力,我们就可以为值类型提供正确且有用的实现IEquatable
public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}
#2
7
I believe getting something as simple as checking objects for equality correct is a bit tricky with .NET's design.
我相信得到一些简单的东西,比如检查对象是否正确,对于.NET的设计来说有点棘手。
For Struct
对于Struct
1) Implement IEquatable<T>
. It improves performance noticeably.
1)实现IEquatable
2) Since you're having your own Equals
now, override GetHashCode
, and to be consistent with various equality checking override object.Equals
as well.
2)因为你现在拥有自己的Equals,所以重写GetHashCode,并与各种相等性检查覆盖object.Equals一致。
3) Overloading ==
and !=
operators need not be religiously done since the compiler will warn if you unintentionally equate a struct with another with a ==
or !=
, but its good to do so to be consistent with Equals
methods.
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...");
}
}
For Class
上课
From MS:
来自MS:
Most reference types should not overload the equality operator, even if they override Equals.
大多数引用类型不应重载相等运算符,即使它们重写等于。
To me ==
feels like value equality, more like a syntactic sugar for Equals
method. Writing a == b
is much more intuitive than writing a.Equals(b)
. Rarely we'll need to check reference equality. In abstract levels dealing with logical representations of physical objects this is not something we would need to check. I think having different semantics for ==
and Equals
can actually be confusing. I believe it should have been ==
for value equality and Equals
for reference (or a better name like IsSameAs
) equality in the first place. I would love to not take MS guideline seriously here, not just because it isn't natural to me, but also because overloading ==
doesn't do any major harm. That's unlike not overriding non-generic Equals
or GetHashCode
which can bite back, because framework doesn't use ==
anywhere but only if we ourself use it. The only real benefit I gain from not overloading ==
and !=
will be the consistency with design of the entire framework over which I have no control of. And that's indeed a big thing, so sadly I will stick to it.
对我来说==感觉像价值平等,更像是Equals方法的语法糖。写a == b比写a.Equals(b)更直观。我们很少需要检查参考平等。在处理物理对象的逻辑表示的抽象级别中,这不是我们需要检查的。我认为为==和Equals设置不同的语义实际上可能令人困惑。我认为首先应该是==对于值相等和等于引用(或更好的名称,如IsSameAs)相等。我不想在这里认真对待MS指南,不仅因为它对我来说不自然,而且因为超载==没有造成任何重大伤害。这与不重写非泛型Equals或GetHashCode不同,它可以咬回来,因为框架不会在任何地方使用==但只有在我们自己使用它的时候。我没有超载==和!=获得的唯一真正的好处是与我无法控制的整个框架的设计的一致性。这确实很重要,所以很遗憾我会坚持下去。
With reference semantics (mutable objects)
使用引用语义(可变对象)
1) Override Equals
and GetHashCode
.
1)覆盖Equals和GetHashCode。
2) Implementing IEquatable<T>
isn't a must, but will be nice if you have one.
2)实现IEquatable
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...");
}
}
With value semantics (immutable objects)
使用值语义(不可变对象)
This is the tricky part. Can get easily messed up if not taken care..
这是棘手的部分。如果不加以照顾,很容易搞砸..
1) Override Equals
and GetHashCode
.
1)覆盖Equals和GetHashCode。
2) Overload ==
and !=
to match Equals
. Make sure it works for nulls.
2)重载==和!=匹配等于。确保它适用于空值。
2) Implementing IEquatable<T>
isn't a must, but will be nice if you have one.
2)实现IEquatable
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...");
}
}
Take special care to see how it should fare if your class can be inherited, in such cases you will have to determine if a base class object can be equal to a derived class object. Ideally, if no objects of derived class is used for equality checking, then a base class instance can be equal to a derived class instance and in such cases, there is no need to check Type
equality in generic Equals
of base class.
如果您的类可以继承,请特别注意它应该如何运行,在这种情况下,您必须确定基类对象是否可以等于派生类对象。理想情况下,如果没有派生类的对象用于相等性检查,那么基类实例可以等于派生类实例,在这种情况下,不需要在基类的通用Equals中检查Type相等性。
In general take care not to duplicate code. I could have made a generic abstract base class (IEqualizable<T>
or so) as a template to allow re-use easier, but sadly in C# that stops me from deriving from additional classes.
一般注意不要重复代码。我可以创建一个通用的抽象基类(IEqualizable
#3
4
Upon reading MSDN, I'm pretty certain the best example of a proper implementation is in the IEquatable.Equals Method page. My only deviation is the following:
在阅读MSDN之后,我非常确定正确实现的最佳示例是在IEquatable.Equals Method页面中。我唯一的偏差如下:
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);
}
For those wondering about the deviation, it derives from the Object.Equals(Object) MSDN page:
对于那些想知道偏差的人,它来自Object.Equals(Object)MSDN页面:
Implementations of Equals must not throw exceptions.
Equals的实现不得抛出异常。
#4
4
I found another reference, it's the .NET Anonymous Type implementation. For an anonymous type with an int and a double as properties I disassembled the following C# code:
我发现了另一个引用,它是.NET Anonymous Type实现。对于具有int和double属性的匿名类型,我反汇编了以下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();
}
}
#5
1
I only have to derive from this class
我只需要从这个班级派生出来
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);
}
And then implement the abstract method like this
然后实现这样的抽象方法
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);
}
}
#1
21
Implementing IEquatable<T>
for a Value Type
Implementing IEquatable<T>
for a value type is a little bit different than for a reference type. Let's assume we have the Implement-Your-Own-Value-Type archetype, a Complex number struct.
为值类型实现IEquatable
public struct Complex
{
public double RealPart { get; set; }
public double ImaginaryPart { get; set; }
}
Our first step would be to implement IEquatable<T>
and override Object.Equals
and Object.GetHashCode
:
我们的第一步是实现IEquatable
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();
}
With very little effort we have a correct implementation, excepting the operators. Adding the operators is also a trivial process:
除了操作员之外,我们只需要很少的努力就能得到正确的实施。添加运算符也是一个简单的过程:
public static bool operator ==(Complex term1, Complex term2)
{
return term1.Equals(term2);
}
public static bool operator !=(Complex term1, Complex term2)
{
return !term1.Equals(term2);
}
An astute reader would notice that we should probably implement IEquatable<double>
since Complex
numbers could be interchangeable with the underlying value type.
精明的读者会注意到我们应该实现IEquatable
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;
}
}
We need four operators if we add IEquatable<double>
, because you can have Complex == double
or double == Complex
(and the same for operator !=
):
如果我们添加IEquatable
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);
}
So there you have it, with minimal effort we have a correct and useful implementation IEquatable<T>
for a value type:
所以你有了它,只需要很少的努力,我们就可以为值类型提供正确且有用的实现IEquatable
public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}
#2
7
I believe getting something as simple as checking objects for equality correct is a bit tricky with .NET's design.
我相信得到一些简单的东西,比如检查对象是否正确,对于.NET的设计来说有点棘手。
For Struct
对于Struct
1) Implement IEquatable<T>
. It improves performance noticeably.
1)实现IEquatable
2) Since you're having your own Equals
now, override GetHashCode
, and to be consistent with various equality checking override object.Equals
as well.
2)因为你现在拥有自己的Equals,所以重写GetHashCode,并与各种相等性检查覆盖object.Equals一致。
3) Overloading ==
and !=
operators need not be religiously done since the compiler will warn if you unintentionally equate a struct with another with a ==
or !=
, but its good to do so to be consistent with Equals
methods.
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...");
}
}
For Class
上课
From MS:
来自MS:
Most reference types should not overload the equality operator, even if they override Equals.
大多数引用类型不应重载相等运算符,即使它们重写等于。
To me ==
feels like value equality, more like a syntactic sugar for Equals
method. Writing a == b
is much more intuitive than writing a.Equals(b)
. Rarely we'll need to check reference equality. In abstract levels dealing with logical representations of physical objects this is not something we would need to check. I think having different semantics for ==
and Equals
can actually be confusing. I believe it should have been ==
for value equality and Equals
for reference (or a better name like IsSameAs
) equality in the first place. I would love to not take MS guideline seriously here, not just because it isn't natural to me, but also because overloading ==
doesn't do any major harm. That's unlike not overriding non-generic Equals
or GetHashCode
which can bite back, because framework doesn't use ==
anywhere but only if we ourself use it. The only real benefit I gain from not overloading ==
and !=
will be the consistency with design of the entire framework over which I have no control of. And that's indeed a big thing, so sadly I will stick to it.
对我来说==感觉像价值平等,更像是Equals方法的语法糖。写a == b比写a.Equals(b)更直观。我们很少需要检查参考平等。在处理物理对象的逻辑表示的抽象级别中,这不是我们需要检查的。我认为为==和Equals设置不同的语义实际上可能令人困惑。我认为首先应该是==对于值相等和等于引用(或更好的名称,如IsSameAs)相等。我不想在这里认真对待MS指南,不仅因为它对我来说不自然,而且因为超载==没有造成任何重大伤害。这与不重写非泛型Equals或GetHashCode不同,它可以咬回来,因为框架不会在任何地方使用==但只有在我们自己使用它的时候。我没有超载==和!=获得的唯一真正的好处是与我无法控制的整个框架的设计的一致性。这确实很重要,所以很遗憾我会坚持下去。
With reference semantics (mutable objects)
使用引用语义(可变对象)
1) Override Equals
and GetHashCode
.
1)覆盖Equals和GetHashCode。
2) Implementing IEquatable<T>
isn't a must, but will be nice if you have one.
2)实现IEquatable
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...");
}
}
With value semantics (immutable objects)
使用值语义(不可变对象)
This is the tricky part. Can get easily messed up if not taken care..
这是棘手的部分。如果不加以照顾,很容易搞砸..
1) Override Equals
and GetHashCode
.
1)覆盖Equals和GetHashCode。
2) Overload ==
and !=
to match Equals
. Make sure it works for nulls.
2)重载==和!=匹配等于。确保它适用于空值。
2) Implementing IEquatable<T>
isn't a must, but will be nice if you have one.
2)实现IEquatable
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...");
}
}
Take special care to see how it should fare if your class can be inherited, in such cases you will have to determine if a base class object can be equal to a derived class object. Ideally, if no objects of derived class is used for equality checking, then a base class instance can be equal to a derived class instance and in such cases, there is no need to check Type
equality in generic Equals
of base class.
如果您的类可以继承,请特别注意它应该如何运行,在这种情况下,您必须确定基类对象是否可以等于派生类对象。理想情况下,如果没有派生类的对象用于相等性检查,那么基类实例可以等于派生类实例,在这种情况下,不需要在基类的通用Equals中检查Type相等性。
In general take care not to duplicate code. I could have made a generic abstract base class (IEqualizable<T>
or so) as a template to allow re-use easier, but sadly in C# that stops me from deriving from additional classes.
一般注意不要重复代码。我可以创建一个通用的抽象基类(IEqualizable
#3
4
Upon reading MSDN, I'm pretty certain the best example of a proper implementation is in the IEquatable.Equals Method page. My only deviation is the following:
在阅读MSDN之后,我非常确定正确实现的最佳示例是在IEquatable.Equals Method页面中。我唯一的偏差如下:
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);
}
For those wondering about the deviation, it derives from the Object.Equals(Object) MSDN page:
对于那些想知道偏差的人,它来自Object.Equals(Object)MSDN页面:
Implementations of Equals must not throw exceptions.
Equals的实现不得抛出异常。
#4
4
I found another reference, it's the .NET Anonymous Type implementation. For an anonymous type with an int and a double as properties I disassembled the following C# code:
我发现了另一个引用,它是.NET Anonymous Type实现。对于具有int和double属性的匿名类型,我反汇编了以下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();
}
}
#5
1
I only have to derive from this class
我只需要从这个班级派生出来
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);
}
And then implement the abstract method like this
然后实现这样的抽象方法
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);
}
}