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.NaNpublicclassNanComparer : IEqualityComparer
{
publicnewbool Equals(object x, object y)
{
if (x isdouble)
return (double) x == (double) y;
elsereturn EqualityComparer<object>.Default.Equals(x, y);
}
publicint GetHashCode(object obj)
{
return EqualityComparer<object>.Default.GetHashCode(obj);
}
}
// C#'s Array implements IStructualEquatable but does not implement IEquatabledouble[] 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 falseIStructuralEquatable 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespaceSystem.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}")]publicpartialstructImmutableArray<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, use IStructuralEquatable.Equals() to test for structural equality. Use StructuralComparisons.StructuralEqualityComparer for simple structural equality, or a custom IEqualityComparer otherwise.
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.
Consider implementing IStructuralComparable and IStructuralEquatable on custom collections.