Exploring the .NET CoreFX Part 7: Reference Versus Structural Equality

This is part 7 of my Exploring the .NET CoreFX Series.

In the previous post, I referenced EqualityComparer.Default. If T does not implement IEquatable, EqualityComparer.Default will use the framework-defined Object.Equals(), which implements reference equality.

However, many times you want to compare two types for structural equality (i.e. identical content) rather than reference equality (i.e. two references point to the same instance of the class). The interface IStructuralEquatable was defined to allow a class to explicitly implement structural, rather than reference equality. Related classes include IStructuralComparable and StructuralComparisons.

IStructuralEquatable.Equals() also accepts a user-provided IEqualityComparer which will be used to compare the object’s member variables for equality.

Here’s some sample code which demonstrates its use:

// A comparer that considers double.NaN != double.NaN
public class NanComparer : IEqualityComparer
{
   public new bool Equals(object x, object y)
   {
      if (x is double)
         return (double) x == (double) y;
      else 
         return EqualityComparer<object>.Default.Equals(x, y);
   }

   public int GetHashCode(object obj)
   {
      return EqualityComparer<object>.Default.GetHashCode(obj);
   }
}

// C#'s Array implements IStructualEquatable but does not implement IEquatable
double[] array1 = { double.NaN, 1.0, 2.0 };
double[] array2 = { double.NaN, 1.0, 2.0 };

// Compare the arrays for equality using Object.Equals() (reference equality).
Console.WriteLine(array1.Equals(array2)); // outputs false

IStructuralEquatable equ = array1;

// Call IStructuralEquatable.Equals using default comparer.
// EqualityComparer<object>.Default.Equals considers double.NaN to
// be equal to itself.
Console.WriteLine(equ.Equals(array2,
    EqualityComparer<object>.Default)); // outputs true

// Call IStructuralEquatable.Equals using
// StructuralComparisons.StructuralEqualityComparer.  This falls back
// to EqualityComparer<object>.Default.Equals.
Console.WriteLine(equ.Equals(array2,
    StructuralComparisons.StructuralEqualityComparer)); // outputs true

// Call IStructuralEquatable.Equals using NanComparer.
Console.WriteLine(equ.Equals(array2,
    new NanComparer())); // outputs false because NaN != NaN

The .NET Core’s ImmutableArray class implements IStructuralEquatable:

namespace System.Collections.Immutable
{
    /// <summary>
    /// A readonly array with O(1) indexable lookup time.
    /// </summary>
    /// <typeparam name="T">The type of element stored by the array.</typeparam>
    [DebuggerDisplay("{DebuggerDisplay,nq}")]
    public partial struct ImmutableArray<T> : IReadOnlyList<T>, IList<T>,
        IEquatable<ImmutableArray<T>>, IImmutableList<T>, IList,
        IImmutableArray, IStructuralComparable, IStructuralEquatable
    {
        ...
    }
}

It is unclear to me why this is the only collection in System.Collections.Immutable to implement IStructuralEquatable.

Recommendations

  1. If a collection implements IStructuralEquatable, use IStructuralEquatable.Equals() to test for structural equality. Use StructuralComparisons.StructuralEqualityComparer for simple structural equality, or a custom IEqualityComparer otherwise.
  2. If a collection implements IStructuralComparable, use IStructuralComparable.CompareTo() to perform a structural comparison. Use StructuralComparisons.StructuralComparer for simple structural comparisons, or a custom IComparer otherwise.
  3. Consider implementing IStructuralComparable and IStructuralEquatable on custom collections.

About Steven Engelhardt, CFA, AIF
Adjunct Professor of Software Engineering at DePaul University • Software Engineering, Data & Analytics in FinTech • Lives in Chicago, IL

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s