When To Use IEquatable<T> And Why
What does IEquatable<T>
buy you, exactly? The only re开发者_StackOverflow中文版ason I can see it being useful is when creating a generic type and forcing users to implement and write a good equals method.
What am I missing?
From the MSDN:
The
IEquatable(T)
interface is used by generic collection objects such asDictionary(TKey, TValue)
,List(T)
, andLinkedList(T)
when testing for equality in such methods asContains
,IndexOf
,LastIndexOf
, andRemove
.
The IEquatable<T>
implementation will require one less cast for these classes and as a result will be slightly faster than the standard object.Equals
method that would be used otherwise. As an example see the different implementation of the two methods:
public bool Equals(T other)
{
if (other == null)
return false;
return (this.Id == other.Id);
}
public override bool Equals(Object obj)
{
if (obj == null)
return false;
T tObj = obj as T; // The extra cast
if (tObj == null)
return false;
else
return this.Id == tObj.Id;
}
I'm amazed that the most important reason is not mentioned here.
IEquatable<>
was introduced mainly for structs for two reasons:
For value types (read structs) the non-generic
Equals(object)
requires boxing.IEquatable<>
lets a structure implement a strongly typedEquals
method so that no boxing is required.For structs, the default implementation of
Object.Equals(Object)
(which is the overridden version inSystem.ValueType
) performs a value equality check by using reflection to compare the values of every field in the type. When an implementer overrides the virtual Equals method in a struct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's field or properties.
Both of which improves performance.
Reference types (read classes) don't benefit as much. The IEquatable<>
implementation does let you avoid a cast from System.Object
but that's a very trivial gain. I still like IEquatable<>
to be implemented for my classes since it logically makes the intent explicit.
Further to the other answers here's a very good reason to be implementing IEquatable<T>
(and obviously overriding Equals(object)
too) for value types. Just look at the default ValueType.Equals(object)
code that gets called otherwise. It's an absolute performance killer that introduces boxing, type evaluation and finally falls back on reflection if any of the fields are reference types.
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
RuntimeType type = (RuntimeType) base.GetType();
RuntimeType type2 = (RuntimeType) obj.GetType();
if (type2 != type)
{
return false;
}
object a = this;
if (CanCompareBits(this))
{
return FastEqualsCheck(a, obj);
}
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < fields.Length; i++)
{
object obj3 = ((RtFieldInfo) fields[i]).UnsafeGetValue(a);
object obj4 = ((RtFieldInfo) fields[i]).UnsafeGetValue(obj);
if (obj3 == null)
{
if (obj4 != null)
{
return false;
}
}
else if (!obj3.Equals(obj4))
{
return false;
}
}
return true;
}
In certain scenarios (such as using the value type as a key in a dictionary) it can murder performance in one foul swoop.
As per the documentation IEquality<T>
is used to improve performance (it prevents boxing.) Especially usefull in generic collections.
If you want to implement IEquatable<T>
in a class hierarchy you can use the following pattern. It prevents derived (including sibling) classes from being equal. If equality is not needed for the derived class you can skip IEquatable<Derived>
but you need to override the CanEqual
to prevent it being equal with base classes (unless of course they should be considered equal).
Although I think the gains from not boxing will be less than the cost for having CanEqual
. In that case you should seal your types and you no longer need CanEqual
. Sealing also has some performance benefits.
public class Base : IEquatable<Base>
{
protected virtual bool CanEqual(Base other) => other is Base;
public override bool Equals(object obj) => obj is Base other && Equals(other);
public bool Equals(Base other) => this.CanEqual(other) && other.CanEqual(this) /* && base logic */;
}
public class Derived : Base, IEquatable<Derived>
{
protected override bool CanEqual(Base other) => other is Derived;
public override bool Equals(object obj) => obj is Derived other && Equals(other);
public bool Equals(Derived other) => this.CanEqual(other) && other.CanEqual(this) /* && derived logic */;
}
精彩评论