c# - 为什么 Contains() 返回 false 但包装在列表中而 Intersect() 返回 true?

标签 c# .net linq

问题:

当我使用 Contains() 时针对 IEnumerable<T>正确实现 IEquatable 的类并覆盖 GetHashCode它返回错误。如果我将匹配目标包装在列表中并执行 Intersect()然后比赛正常进行。我更愿意使用 Contains() .

关于 IEnumerable.Contains()来自 MSDN :

Elements are compared to the specified value by using the default equality comparer

关于 EqualityComparer<T>.Default来自 MSDN 的属性(property):

The Default property checks whether type T implements the System.IEquatable generic interface and if so returns an EqualityComparer that uses that implementation. Otherwise it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.

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

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

我错过了什么?我是否也必须像 MSDN 文章那样创建一个相等比较器? MSDN 建议有 IEquatable就足够了, 会帮我把它包起来。

示例控制台应用

注意:GetHashCode()来自 Jon Skeet here

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();
        }
    }
}

.NET 4.5 Fiddle Example

最佳答案

好的 - 问题实际上出在 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
}

Enumerable.Contains代表ICollection<T>.Contains如果源实现 ICollection<T> ,这就是为什么您获得数组行为而不是 Enumerable.Contains 的原因在您的代码中“手动”实现。

现在ICollection<T>.Contains 是否表示选择使用哪个比较器取决于实现:

Implementations can vary in how they determine equality of objects; for example, List<T> uses Comparer<T>.Default, whereas Dictionary<TKey, TValue> allows the user to specify the IComparer<T> implementation to use for comparing keys.

但是:

  • 该文档已经损坏,因为它应该在谈论 EqualityComparer<T>IEqualityComparer<T> , 不是 Comparer<T>IEqualityComparer<T>
  • 数组决定使用既未明确指定也未默认的比较器 EqualityComparer<T>我觉得很不自然。

解决方案 是覆盖 object.Equals(object) :

public override bool Equals(object other)
{
    return Equals(other as MyEquatable);
}

同时实现这两个 IEquatable<T> 通常是令人愉快的覆盖object.Equals(object) ,为了一致性。因此,虽然您的代码应该在我看来已经可以工作了

关于c# - 为什么 Contains() 返回 false 但包装在列表中而 Intersect() 返回 true?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25419959/

相关文章:

.net - WPF 错误 CS0433

asp.net-mvc - 绑定(bind)排除 Asp.net MVC 不适用于 LINQ 实体

c# - Web API 绑定(bind)不可变模型

c# - 使用 ASP.Net MVC 2 脚手架创建编辑表单

c# - 如何使用不属于桌面的显示器 (Windows 7)

.net - 我可以在 Word 或 Excel 中创建撤消事务吗? (VSTO)

c# - 如何在当前程序集中查找具有特定名称的 C# 接口(interface)的实现?

c# - 在 LINQ 中表达 Oracle LISTAGG() 功能?

c# - GridViewRow 单元格在按钮中返回空字符串单击事件在 Gridview 中启动

c# - 找出方法可能在 C# 中抛出的异常