Implementing Object Comparison

The .NET framework gives us the object base class and the Equals() operator. Then there are the == and the != operators as language features of C# and other managed languages.

It also isn't clearly stated whether Equals() is supposed to compare object identity (check if two objects are the exact same instance) or object state (check if two objects carry the same values) and unless you explicitely implement the Equals() method, an object identity comparison is done.

Since you can always do an object identity comparison by using the ReferenceEquals() method, you should always write your comparison operators so they do an object state comparison. In the few special cases where you really need object identity comparison, you can either create an adapter class or explicitely state this behavior in the documentation for your Equals() operator.

The Equals() Method

My suggestion to you is to actually implement two Equals() methods:

/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public override bool Equals(object other) {
  return Equals(other as MyClass);
}

/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public virtual bool Equals(MyClass other) {
  if(ReferenceEquals(other, null))
    return false;

  return (this.Start == other.Start) && (this.End == other.End);
}

Let's see: If someone compares an instance of MyClass against an object of another type, the Equals(object other) method is called, where the as operator will return null since the cast is not valid, effectively doing a null comparison which will always compare as false (not equal).

The specialized Equals() method provides a minor performance gain since we can skip the up- and downcasting otherwise associated with the Equals() method. And since this can never be null for an instance method, we can safely assume that if the comparison object is null the comparison result is false.

If both objects are valid and of the same type, we proceed to compare the non-mutable fields of both instances, doing the actual object state comparison.

The Comparison Operators

These can make use of the specialized Equals() operator and are therefore easy to implement:

/// <summary>Checks two segment instances for inequality</summary>
/// <param name="first">First instance to be compared</param>
/// <param name="second">Second instance fo tbe compared</param>
/// <returns>True if the instances differ or exactly one reference is set to null</returns>
public static bool operator !=(MyClass first, MyClass second) {
  return !(first == second);
}

/// <summary>Checks two segment instances for equality</summary>
/// <param name="first">First instance to be compared</param>
/// <param name="second">Second instance fo tbe compared</param>
/// <returns>True if both instances are equal or both references are null</returns>
public static bool operator ==(MyClass first, MyClass second) {
  if(ReferenceEquals(first, null))
    return ReferenceEquals(second, null);

  return first.Equals(second);
}

As you can see, the entire logic has been moved to the == operator to avoid duplicating code lines.

Of course, since the comparison operators are static methods, we now might encounter the case where the left operand is null. This is handled by checking whether the left operand is null and then only returning true if the right operand is also null (so null == null would evaluate as true). If only the right operand is null, this will be handled by the Equals() operator.

Drawbacks

The only drawback of this implementation is that you have to implement the specialized Equals() operator in all derived classes and obtain one more Equals() specialization for each level of inheritance. You might want to accept the performance loss of the up- and redowncast dilemma in some special cases and only implement an Equals(object other) operator there.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <br> <a> <em> <strong> <u> <i> <b> <cite> <blockcode> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <pre> <span>
  • You can highlight code with any of the following tags: <blockcode>

More information about formatting options