Saturday, November 29, 2008

Equality Now!

There are three primary ways to handle equality in .NET: overriding Object.Equals, overloading the == and != operators, and implementing IEquatable<T>. Framework Design Guidelines offers pretty good advice on when to use what. A quick summary:
  • 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);
}

The purpose of the above patterns is to centralize the equality logic in one place. You'll notice in the example which uses all three approaches, the == operator is the only place with actual equality logic. I find this just makes thing easier. If you want, you can have custom logic in the != overload (the logical inverse of ==), but that means you have to make changes in two places if you alter the equality logic, and it's really easy to make a mistake with logic operators.

Want to share your tips for equality? Have a better pattern? Leave a comment!

9 comments:

Lex Li said...

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.

Anonymous said...

Don't forget that if you override equals, you *must* override GetHashCode properly too.

Scott Ellington said...

I've used the same pattern for operator overloads. it's good to see someone else doing it this way

benji said...

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 ¬_¬.

Anonymous said...

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.

Scott said...

"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.

Lex Li said...

I don't agree with benji. It is not recommended to throw exceptions in the equality methods.

Scott said...

I think benji was being sarcastic. (I hope!)

LogicMagic said...

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