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!