HashSet <t> .CreateSetComparer()不能指定IEqalityComparer <t>,有替代方法吗?

时间:2018-02-17 14:43:26

标签: c# hashset iequalitycomparer

internal source中有一个构造函数public HashSetEqualityComparer(IEqualityComparer<T> comparer),但它是内部的,所以我无法使用它。

默认情况下,HashSet<T>.CreateSetComparer()只使用无参数构造函数,该构造函数将应用EqualityComparer<T>.Default

有没有办法让HashSetEqualityComparer<T>选择IEqualityComparer<T>,而不从源代码中复制代码?

3 个答案:

答案 0 :(得分:3)

我认为最佳解决方案是使用SetEquals。它完成了您所需要的工作,其工作方式与HashSetEqualityComparer完全相同,但考虑其比较中定义的任何自定义比较器。

因此,在您希望使用HashSet<T>作为字典键的特定场景中,您需要实现一个使用IEqualityComparer<HashSet<T>>和&#34的SetEquals ;借用&#34; 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;
    }
}

但是,是的,这是一个小小的痛苦,没有办法直接创建一个利用自定义比较器的SetEqualityComparer,但这种不幸的行为是由于,恕我直言,更多的是现有实施的错误,而不是缺乏需要超载;没有理由CreateSetComparer()无法返回IEqualityComparer实际使用其比较的集合的比较器,如上面的代码所示。

如果我有声音,CreateSetComparer()根本不是静态方法。然后很明显,或者至少可以预测,无论返回什么比较器都将使用当前设置的比较器创建。

答案 1 :(得分:1)

我同意@InBetween,使用SetEquals是最好的方法。即使添加构造函数仍然无法实现你想要的。

请看这段代码: enter image description here

我试着这样做:

class HashSetEqualityComparerWrapper<T> : IEqualityComparer<HashSet<T>>
{
    static private Type HashSetEqualityComparerType = HashSet<T>.CreateSetComparer().GetType();
    private IEqualityComparer<HashSet<T>> _comparer;

    public HashSetEqualityComparerWrapper()
    {
        _comparer = HashSet<T>.CreateSetComparer();
    }
    public HashSetEqualityComparerWrapper(IEqualityComparer<T> comparer)
    {
        _comparer = HashSet<T>.CreateSetComparer();
        if (comparer != null)
        {
            FieldInfo m_comparer_field = HashSetEqualityComparerType.GetField("m_comparer", BindingFlags.NonPublic | BindingFlags.Instance);
            m_comparer_field.SetValue(_comparer, comparer);
        }
    }

    public bool Equals(HashSet<T> x, HashSet<T> y)
    {
        return _comparer.Equals(x, y);
    }
    public int GetHashCode(HashSet<T> obj)
    {
        return _comparer.GetHashCode(obj);
    }
}

更新

我花了5分钟来实现另一个版本的HashSetEqualityComparer<T>源代码。并重写bool Equals(HashSet<T> x, HashSet<T> y)方法。这并不复杂。所有代码都只是从源代码复制和粘贴,我只是修改了一下。

class CustomHashSetEqualityComparer<T> : IEqualityComparer<HashSet<T>>
{
    private IEqualityComparer<T> m_comparer;

    public CustomHashSetEqualityComparer()
    {
        m_comparer = EqualityComparer<T>.Default;
    }

    public CustomHashSetEqualityComparer(IEqualityComparer<T> comparer)
    {
        if (comparer == null)
        {
            m_comparer = EqualityComparer<T>.Default;
        }
        else
        {
            m_comparer = comparer;
        }
    }

    // using m_comparer to keep equals properties in tact; don't want to choose one of the comparers
    public bool Equals(HashSet<T> x, HashSet<T> y)
    {
        // http://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,1360
        // handle null cases first
        if (x == null)
        {
            return (y == null);
        }
        else if (y == null)
        {
            // set1 != null
            return false;
        }

        // all comparers are the same; this is faster
        if (AreEqualityComparersEqual(x, y))
        {
            if (x.Count != y.Count)
            {
                return false;
            }
        }
        // n^2 search because items are hashed according to their respective ECs
        foreach (T set2Item in y)
        {
            bool found = false;
            foreach (T set1Item in x)
            {
                if (m_comparer.Equals(set2Item, set1Item))
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                return false;
            }
        }
        return true;
    }

    public int GetHashCode(HashSet<T> obj)
    {
        int hashCode = 0;
        if (obj != null)
        {
            foreach (T t in obj)
            {
                hashCode = hashCode ^ (m_comparer.GetHashCode(t) & 0x7FFFFFFF);
            }
        } // else returns hashcode of 0 for null hashsets
        return hashCode;
    }

    // Equals method for the comparer itself. 
    public override bool Equals(Object obj)
    {
        CustomHashSetEqualityComparer<T> comparer = obj as CustomHashSetEqualityComparer<T>;
        if (comparer == null)
        {
            return false;
        }
        return (this.m_comparer == comparer.m_comparer);
    }

    public override int GetHashCode()
    {
        return m_comparer.GetHashCode();
    }

    static private bool AreEqualityComparersEqual(HashSet<T> set1, HashSet<T> set2)
    {
        return set1.Comparer.Equals(set2.Comparer);
    }
}

答案 2 :(得分:1)

如果使用自定义比较器,请避免此类。它使用自己的相等比较器执行GetHashCode,但是在执行Equals(Set1,Set2)时,如果Set1和Set2具有相同的相等比较器,则HashSetEqualityComparer将使用集合的比较器。如果Set1和Set2具有不同的比较器,则HashsetEqualityComparer仅将其自己的比较器用于等于

情况变得更糟。它调用了HashSet.HashSetEquals,其中包含一个错误(请参阅this行1489,该行在执行子集检查之前缺少if (set1.Count != set2.Count) return false

该错误由以下程序说明:

class Program
{
    private class MyEqualityComparer : EqualityComparer<int>
    {
        public override bool Equals(int x, int y)
        {
            return x == y;
        }

        public override int GetHashCode(int obj)
        {
            return obj.GetHashCode();
        }
    }

    static void Main(string[] args)
    {
        var comparer = HashSet<int>.CreateSetComparer();
        var set1 = new HashSet<int>(new MyEqualityComparer()) { 1 };
        var set2 = new HashSet<int> { 1, 2 };

        Console.WriteLine(comparer.Equals(set1, set2));
        Console.WriteLine(comparer.Equals(set2, set1)); //True!

        Console.ReadKey();
    }
}

关于此问题的其他答案(我没有代表要评论):

  • 廖伟强:他的回答也包含错误,因为它是从参考源复制而来的
  • InBetween:解决方案不对称。 CustomHashSetEqualityComparer.Equals(A,B)并不总是等于CustomHashSetEqualityComparer.Equals(B,A)。我会为此感到害怕。

我认为,如果健壮的实现遇到比较器与其本身不同的集合,则应抛出异常。它可以始终使用自己的比较器,而忽略设置的比较器,但这会带来奇怪且不直观的行为。