当T为IEnumerable时,为IEnumerables <t>创建自定义相等比较器

时间:2018-02-18 18:59:41

标签: c# iequalitycomparer

我想要一个自定义的相等比较器IEnumerable。使用 @PaulZahra &#39; code,我创建了以下类:

class CustomEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        var enumerables = new Dictionary<T, uint>();

        foreach (T item in x)
        {
            enumerables.Add(item, 1);
        }

        foreach (T item in y)
        {
            if (enumerables.ContainsKey(item))
            {
                enumerables[item]--;
            }
            else
            {
                return false;
            }
        }

        return enumerables.Values.All(v => v == 0);
    }

    public int GetHashCode(IEnumerable<T> obj) => obj.GetHashCode();
}

问题是如果T本身是IEnumerable,那么ContainsKey将检查引用相等性,而此相等比较器的点是检查值相等在任何给定的深度

我想改为使用.Keys.Contains(),因为它可以接受IEqualityComparer作为参数:

if (enumerables.Keys.Contains(item, this)) // not sure if "this" or a new object

但是我收到以下错误( CS1929 ):

  

&#39; Dictionary.KeyCollection&#39;不包含&#39;包含&#39;的定义和最好的扩展方法重载&#39; Queryable.Contains(IQueryable,T,IEqualityComparer)&#39;需要一个类型为“IQueryable&#39;

的接收器

我不知道如何处理这个问题。怎么解决?感谢。

修改:请注意,此比较器并不关心订单。

2 个答案:

答案 0 :(得分:2)

要拥有这样的递归比较器,如果Dictionary是可枚举的,您只需要将适当的比较器传递给T。我认为getting type T from IEnumerable<T>然后相当于new Dictionary<U, uint>(new CustomEqualityComparer<U>)(使用Create instance of generic type?)应该达到你想要的效果。

注意:

  • 如果您对任何字典/ GetHashCode使用comparer,则必须提供与Equals匹配的HashSet的正确实现。序列的默认Equals是与您的Equals不对齐的引用比较。请注意,GetHashCode的大多数实现取决于集合中项目的顺序 - 因此您需要找到适用于集合的项目。即每个项目的哈希码总和会做,可能会使分布略差。
  • 您可能希望LINQ设置操作而不是手动执行。像Distinct这样的所有操作都已经采用了比较器。如果“设置相同”,您可以使用Distinct - x.Distinct(y, comparerBuiltViaReflection)
  • 谨防此类代码的局限性:并非每个枚举都可以多次枚举(用户输入,网络流,...),或者可能在重新迭代(while(count < 10){ count ++; yield return random.Next(); })时产生不同的结果, iteartion很重要(在每次迭代时重读大文件中的所有行)或者枚举可以表示无限序列(while(true){ yield return count++; })。

答案 1 :(得分:1)

  • 正如其他人所提到的,IEnumerable<T>可以永远枚举,因此在该界面上执行此操作会很危险。我建议使用ICollection<T>代替它 - 它有一个固定的大小。而且你会发现它适用于你想要使用的大多数类型。

  • 此外,我建议使用TryGetValue来减少查询字典所需的次数。

  • 您的代码未正确保存第一个可枚举项中每个项目的数量。

  • GetHashCode需要考虑可枚举的每个成员。

所有这一切,这里是对您的实施的调整:

class CustomEqualityComparer<T> : IEqualityComparer<ICollection<T>>
{
    public bool Equals(ICollection<T> x, ICollection<T> y)
    {
        if (x.Count != y.Count) return false;
        var enumerables = new Dictionary<T, uint>(x.Count);

        foreach (T item in x)
        {
            enumerables.TryGetValue(item, out var value);
            enumerables[item] = value + 1;
        }

        foreach (T item in y)
        {
            var success = enumerables.TryGetValue(item, out var value);
            if (success)
            {
                enumerables[item] = value - 1;
            }
            else
            {
                return false;
            }
        }

        return enumerables.Values.All(v => v == 0);
    }

    public int GetHashCode(ICollection<T> obj)
    {
         unchecked
         {
             var hashCode = 0;

             foreach(var item in obj)
             {
                 hashCode += (item != null ? item.GetHashCode() : 0);                
             }
             return hashCode;
         }

     }
}