为什么Contains()返回false但是在列表中包装并且Intersect()返回true?

Dr *_*ang 8 .net c# linq

问题:

当我使用Contains()针对IEnumerable<T>的是正确地实现类IEquatable和覆盖GetHashCode返回false.如果我将匹配目标包装在列表中并执行,Intersect()则匹配正常.我更愿意使用Contains().

IEnumerable.Contains()MSDN:

使用默认的相等比较器将元素与指定值进行比较

EqualityComparer<T>.Default物业从MSDN:

Default属性检查类型T是否实现System.IEquatable泛型接口,如果是,则返回使用该实现的EqualityComparer.否则返回一个EqualityComparer,它使用由T提供的Object.Equals和Object.GetHashCode的覆盖.

据我所知,IEquatable<T>在我的类上实现应该意味着Equals在尝试查找匹配时默认比较器使用该方法.我想使用Equals,因为我希望只有一种方式,两个对象是相同的,我不想要开发人员必须记住的策略.

我觉得奇怪的是,如果我将匹配目标包装在a中List然后执行a ,则Intersect匹配被正确找到.

我错过了什么?我是否必须创建一个相等比较器,如MSDN文章?MSDN建议有IEquatable足够的东西,会包装给我.

控制台应用示例

NB:GetHashCode()来自Jon Skeet

using System;
using System.Collections.Generic;
using System.Linq;

namespace ContainsNotDoingWhatIThoughtItWould
{
    class Program
    {
        public class MyEquatable : IEquatable<MyEquatable>
        {
            string[] tags;

            public MyEquatable(params string[] tags)
            {
                this.tags = tags;
            }

            public bool Equals(MyEquatable other)
            {
                if (other == null)
                {
                    return false;
                }

                if (this.tags.Count() != other.tags.Count())
                {
                    return false;
                }
                var commonTags = this.tags.Intersect(other.tags);
                return commonTags.Count() == this.tags.Count();
            }

            public override int GetHashCode()
            {
                int hash = 17;
                foreach (string element in this.tags.OrderBy(x => x))
                {
                    hash = unchecked(hash * element.GetHashCode());
                }
                return hash;
            }
        }

        static void Main(string[] args)
        {
            // Two objects for the search list
            var a = new MyEquatable("A");
            var ab = new MyEquatable("A", "B");

            IEnumerable<MyEquatable> myList = new MyEquatable[] 
            { 
                a, 
                ab 
            };

            // This is the MyEquatable that we want to find
            var target = new MyEquatable("A", "B");

            // Check that the equality and hashing works
            var isTrue1 = target.GetHashCode() == ab.GetHashCode();
            var isTrue2 = target.Equals(ab);


            var isFalse1 = target.GetHashCode() == a.GetHashCode();
            var isFalse2 = target.Equals(a);

            // Why is this false?
            var whyIsThisFalse = myList.Contains(target);

            // If that is false, why is this true?
            var wrappedChildTarget = new List<MyEquatable> { target };

            var thisIsTrue = myList.Intersect(wrappedChildTarget).Any();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

.NET 4.5小提琴示例

Jon*_*eet 7

好的 - 问题实际上是在数组实现中ICollection<T>.Contains.你可以看到这样:

static void Main(string[] args)
{
    var ab = new MyEquatable("A", "B");
    var target = new MyEquatable("A", "B");

    var array = new[] { ab };
    Console.WriteLine(array.Contains(target)); // False

    var list = new List<MyEquatable> { ab };
    Console.WriteLine(list.Contains(target));  // True

    var sequence = array.Select(x => x);
    Console.WriteLine(sequence.Contains(target)); // True
}
Run Code Online (Sandbox Code Playgroud)

Enumerable.Contains委托ICollection<T>.Contains如果源实现ICollection<T>,这就是为什么你得到数组行为而不是代码中的Enumerable.Contains"长手"实现.

现在ICollection<T>.Contains 确实说实现选择使用哪个比较器:

实现可以在决定对象相等性方面有所不同; 例如,List<T>使用Comparer<T>.Default,而Dictionary<TKey, TValue>允许用户指定IComparer<T>用于比较密钥的实现.

但:

  • 该文档已经被打破,因为它应该讨论,EqualityComparer<T>IEqualityComparer<T>不是Comparer<T>IEqualityComparer<T>
  • 数组决定使用既没有明确指定也没有默认的比较器EqualityComparer<T>对我来说似乎非常不自然.

解决方案是重写object.Equals(object):

public override bool Equals(object other)
{
    return Equals(other as MyEquatable);
}
Run Code Online (Sandbox Code Playgroud)

为了保持一致性,实现这两者IEquatable<T> 覆盖它通常是令人愉快的object.Equals(object).因此,虽然您的代码应该在我的视图中工作