Search code examples
c#hashsetiequalitycomparer

HashSet<T>.CreateSetComparer() cannot specify IEqualityComparer<T>, is there an alternative?


In the internal source there is such a constructor public HashSetEqualityComparer(IEqualityComparer<T> comparer) but it's internal so I can't use it.

By default, HashSet<T>.CreateSetComparer() just uses the parameterless constructor which will apply EqualityComparer<T>.Default.

Is there a way to get a HashSetEqualityComparer<T> with a IEqualityComparer<T> of choice, without copying out the code from the source?


Solution

  • I think best solution is using SetEquals. It does the job you need and exactly in the same way that HashSetEqualityComparer does but it will account for any custom comparers defined in the sets its comparing.

    So, in your specific scenario where you want to use a HashSet<T> as a key of a dictionary, you need to implement an IEqualityComparer<HashSet<T>> that makes use of SetEquals and "borrows" the reference source of HashSetEqualityComparer.GetHashCode():

    public class CustomHashSetEqualityComparer<T>
        : IEqualityComparer<HashSet<T>>
    {
        public bool Equals(HashSet<T> x, HashSet<T> y)
        {
            if (ReferenceEquals(x, null))
                return false;
    
            return x.SetEquals(y);
        }
    
        public int GetHashCode(HashSet<T> set)
        {
            int hashCode = 0;
    
            if (set != null)
            {
                foreach (T t in set)
                {
                    hashCode = hashCode ^ 
                        (set.Comparer.GetHashCode(t) & 0x7FFFFFFF);
                }
            }
    
            return hashCode;
        }
    }
    

    But yes, its a small pain that there is not way to directly create a SetEqualityComparer that leverages custom comparers but this unfortunate behavior is due, IMHO, more to a bug of the existing implementation than a lack of the needed overload; there is no reason why CreateSetComparer() can't return an IEqualityComparer that actually uses the comparers of the sets its comparing as the code above demonstrates.

    If I had a voice in it, CreateSetComparer() wouldn't be static method at all. It would then be obvious, or at least predictable, that whatever comparer was returned would be created with the current set's comparer.