-1

Let's consider the following naive implementation to check whether a type has a proper EqualityComparer<T>.Default implementation for HashSet:

bool IsHashEqual<T>(Func<T> f) where T : struct, IEquatable<T>
{
    var set = new HashSet<T>();
    set.Add(f());
    set.Add(f());
    return set.Count == 1;
}

We can verify the following:

Assert.True(IsHashEqual(() => 42));
Assert.True(IsHashEqual(() => 3.14));
Assert.True(IsHashEqual(() => true));

With a bit of work, we can extend the above IsHashEqual code and verify this property also on the string class.

However we can make the above code fails with something like:

Assert.False(IsHashEqual(() => ImmutableArray.Create(new byte[] { 0, 1, 2, 3 })));

How can I rework my generic constraints so that it matches the expectation of the default equality comparer (EqualityComparer<T>.Default) ? Or else can I infer from type T that I need to use StructuralComparisons.StructuralEqualityComparer ?

References:

malat
  • 12,152
  • 13
  • 89
  • 158

1 Answers1

1

Default equality comparer checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation.

IsHashEqual(() => ImmutableArray.Create(new byte[] { 0, 1, 2, 3 })) returns false because implementation of method Equals in ImmutableArray just compares references of internal arrays.

If you want your HashSet to compare arrays by values, you need to write your own implementation of IEqualityComparer:

class ImmutableArrayComparer<T> : IEqualityComparer<ImmutableArray<T>>
{
    public bool Equals([AllowNull] ImmutableArray<T> first, [AllowNull] ImmutableArray<T> second)
    {
        return StructuralComparisons.StructuralEqualityComparer.Equals(first, second);
    }

    public int GetHashCode([DisallowNull] ImmutableArray<T> array)
    {
        return StructuralComparisons.StructuralEqualityComparer.GetHashCode(array);
    }
}

Now "naive" implementation works as expected and returns true:

bool IsImmutableHashEqual<T>(Func<ImmutableArray<T>> f)
{
    var set = new HashSet<ImmutableArray<T>>(new ImmutableArrayComparer<T>());
    set.Add(f());
    set.Add(f());
    return set.Count == 1;
}

But keep in mind that if you have a lot of large arrays in HasSet, you may run into performance issues.

Andrey Rodin
  • 133
  • 7
  • The issue remains that I do not know how to instantiate the *right* HashSet based on input type. – malat Sep 20 '21 at 07:36