为什么 Contains() 返回 false 但包装在一个列表中,而 Intersect() 返回 true
本文关键字:返回 列表 一个 Intersect true false Contains 包装 为什么 | 更新日期: 2023-09-27 18:18:52
问题:
当我对正确实现IEquatable
并覆盖GetHashCode
类的IEnumerable<T>
使用Contains()
时,它会返回 false。如果我将匹配目标包装在列表中并执行Intersect()
则匹配工作正常。我更喜欢使用Contains()
.
在 MSDN 的IEnumerable.Contains()
:
使用默认相等比较器将元素与指定值进行比较
在 MSDN 的EqualityComparer<T>.Default
属性上:
Default 属性检查类型 T 是否实现 System.IEquatable 泛型接口,如果是,则返回使用该实现的 EqualityComparer。否则,它将返回一个 EqualityComparer,该比较器使用 Object.Equals 和 Object.GetHashCode 由 T 提供的
覆盖。
据我了解,在我的类上实现IEquatable<T>
应该意味着默认比较器在尝试查找匹配项时使用 Equals
方法。我想使用 Equals,因为我希望只有一种方式使两个对象相同,我不想要开发人员必须记住的策略。
我觉得奇怪的是,如果我将匹配目标包装在List
中,然后执行Intersect
,那么匹配就会正确找到。
我错过了什么?我是否也必须创建一个相等比较器,如 MSDN 文章?MSDN 建议拥有IEquatable
就足够了,它会为我包装它。
示例控制台应用
注意:来自Jon Skeet的GetHashCode()
在这里
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 小提琴示例
好的 - 问题实际上出在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
确实说由实现来选择使用哪个比较器:
实现在确定对象相等性的方式上可能有所不同;例如,
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);
}
为了保持一致性,同时实现IEquatable<T>
和覆盖object.Equals(object)
通常是令人愉快的。因此,虽然您的代码在我看来应该已经可以工作