This is part 7/17 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
- If a collection implements
IStructuralEquatable, useIStructuralEquatable.Equals()to test for structural equality. UseStructuralComparisons.StructuralEqualityComparerfor simple structural equality, or a customIEqualityComparerotherwise. - If a collection implements
IStructuralComparable, useIStructuralComparable.CompareTo()to perform a structural comparison. UseStructuralComparisons.StructuralComparerfor simple structural comparisons, or a customIComparerotherwise. - Consider implementing
IStructuralComparableandIStructuralEquatableon custom collections.