- If you want custom equality logic, whatever else you might do, override Object.Equals. This method is used by the various data structures in System.Collections (and elsewhere in the BCL) to determine equality. It's the first best way to do equality.
- IEquatable<T> should be implemented by structs with custom equality. Because calling Object.Equals on a struct involves boxing, and the implementation for value types uses reflection (!!!!!), IEquatable<T> gets around both of those problems. When implementing IEquatable<T>, always override Object.Equals as well.
- Operator overloading is a little bit tricky because it's really just a compiler feature. The compiler has a lookup rule for the operator implementation which takes the most derived types along the operands' type ancestry. Whereas Object.Equals is a virtual method whose overridden implementation will always be used, overloaded operators will only be used if both operands are of static (compile-time) types that are or derive from the types specified in the overload. Overloading operators is a matter of discretion. It's more commonly done with value than reference types. If you overload the equality operators, also override Equals (an implement IEquatable<T> if the type is a struct).
When you are just overriding Equals, here is the pattern I find works the best:
public override bool Equals (object obj)
{
MyRefType mine = obj as MyRefType;
return mine != null && /* custom equality logic here */;
}
If you want to overload operators and it's a reference type, here's the thing to do:
public override bool Equals (object obj)
{
MyRefType mine = obj as MyRefType;
return mine == this;
}
public static bool operator == (MyRefType mine1, MyRefType mine2)
{
if (Object.ReferenceEquals (mine1, null)) {
return Object.ReferenceEquals (mine2, null);
}
return !Object.ReferenceEquals (mine2, null) &&
/* custom equality logic here /*;
}
public static bool operator != (MyRefType mine1, MyRefType mine2)
{
return !(mine1 == mine2);
}
If you have a value type, here's the scenario without overriding the operators.
public override bool Equals (object obj)
{
return obj is MyValueType && Equals ((MyValueType)obj);
}
public bool Equals (MyValueType mine)
{
return /* custom equality logic here */
}
And here's the value type with operator overloads
public override bool Equals (object obj)
{
return obj is MyValueType && Equals ((MyValueType)obj);
}
public bool Equals (MyValueType mine)
{
return mine == this;
}
public static bool operator == (MyType mine1, MyType mine2)
{
return /* custom equality logic here */
}
public static bool operator != (MyType mine1, MyType mine2)
{
return !(mine1 == mine2);
}
Want to share your tips for equality? Have a better pattern? Leave a comment!
9 comments:
Well, when I designed my own structs, I referred to String.cs in Mono. Meanwhile, I find Effective C# provides good guide on classes.
BTW, I did not have time to finish my copy of Framework Design Guidelines yet.
Don't forget that if you override equals, you *must* override GetHashCode properly too.
I've used the same pattern for operator overloads. it's good to see someone else doing it this way
Clearly
public static bool operator==(T a, T b)
{
if (Object.Equals(a,null) || Object.Equals(b,null))
throw new NullReferenceException();
else /* Custom equality logic here */
}
to stop the all too common .net failure of thinking nulls are acceptable in code and that null checking protects against them ¬_¬.
I find this snippet strange:
public override bool Equals (object obj)
{
MyRefType mine = obj as MyRefType;
return mine != null && /* custom equality logic here */;
}
If class A {} an class B : A {}, then a == b is true, but b == a is false. I suppose you have to check that a reference is actually a member of current class alone.
"If class A {} an class B : A {}, then a == b is true, but b == a is false. I suppose you have to check that a reference is actually a member of current class alone."
Um, not quite. If Equals is overriden in A but not B, then a.Equals (b) and b.Equals (a) will always be true if the two objects pass the equality criteria. If Equals is overridden in both A and B, a.Equals (b) will be true if the criteria is met, but b.Equals (a) will never be true. This is how its supposed to work.
I don't agree with benji. It is not recommended to throw exceptions in the equality methods.
I think benji was being sarcastic. (I hope!)
Since structs are ValueTypes they are not nullable and as such cannot be used in an foo as MyStruct construct which you use in your Rquals() code.
Vlad
Post a Comment