为什么不是';除了(LINQ)正确地比较事物之外?(使用IEquatable)

为什么不是';除了(LINQ)正确地比较事物之外?(使用IEquatable),linq,Linq,我有两个我自己的引用类型对象的集合,我为它们编写了自己的IEquatable.Equals方法,我希望能够对它们使用LINQ方法 所以 List candy=dataSource.GetListOfCandy(); List-lollyPops=dataSource.GetListOfLollyPops(); var candyOtherThanLollyPops=糖果。除了(lollyPops); 根据.Except的文档,不传递IEqualityComparer应导致使用EqualityC

我有两个我自己的引用类型对象的集合,我为它们编写了自己的IEquatable.Equals方法,我希望能够对它们使用LINQ方法

所以

List candy=dataSource.GetListOfCandy();
List-lollyPops=dataSource.GetListOfLollyPops();
var candyOtherThanLollyPops=糖果。除了(lollyPops);
根据.Except的文档,不传递IEqualityComparer应导致使用EqualityComparer.Default来比较对象。默认比较器的文档如下所示:

“默认属性检查类型T是否实现System.IEquatable泛型接口,如果是,则返回使用该实现的EqualityComparer。否则,它返回使用T提供的Object.Equals和Object.GetHashCode重写的EqualityComparer。”

因此,因为我为我的对象实现了IEquatable,所以它应该使用它并工作。但事实并非如此。在重写GetHashCode之前,它不会工作。事实上,如果我设置了一个断点,我的IEquatable.Equals方法永远不会执行。这让我认为,根据B计划的文件,它正在进行。无论如何,我理解重写GetHashCode是一个好主意,我可以让它正常工作,但我感到不安的是,它的行为方式与它自己的文档所述的方式不一致


为什么它不照它说的做呢?谢谢。

冒昧猜测一下,这些课程是不同的吗?我认为默认情况下,IEquatable只适用于同一类。因此,它可能通过返回到对象。相等方法

接口
IEqualityComparer
有以下两种方法:

bool Equals(T x, T y);
int GetHashCode(T obj);
因此,此接口的良好实现将同时实现这两个方面。Linq扩展方法依赖于哈希代码,以便在内部使用字典或集合查找来确定要跳过哪些对象,因此需要正确的GetHashCode实现

不幸的是,当您使用
EqualityComparer.Default
时,该类本身并没有提供良好的GetHashCode实现,并且在检测到对象实现
IEquatable
时,依赖相关对象T类型来提供该部分

这里的问题是,
IEquatable
实际上并没有声明
GetHashCode
,因此与它声明的
Equals
方法相比,忘记正确实现该方法要容易得多

所以你有两个选择:

  • 提供一个适当的
    IEqualityComparer
    实现,该实现同时实现
    Equals
    GetHashCode
  • 确保除了在对象上实现
    IEquatable
    之外,还要实现适当的
    GetHashCode

经过调查,事情并不像我想象的那么糟。基本上,当一切都正确实现时(
GetHashCode
,等等),文档是正确的,行为也是正确的。但是,如果您尝试自行实现
IEquatable
之类的操作,那么您的
Equals
方法将永远不会被调用(这似乎是由于
GetHashCode
未正确实现所致)。因此,虽然文档在技术上是错误的,但它只在一个你永远不想做的边缘情况下是错误的(如果这项调查教会了我什么,那就是
IEquatable
是一整套方法的一部分,你应该以原子方式实现(不幸的是,按惯例,而不是按规则实现))

这方面的好消息来源有:


我编写了一个GenericEqualityComparer,用于动态处理以下类型的方法:Distinct、Except、Intersect等

使用方法如下:

var results = list1.Except(list2, new GenericEqualityComparer<MYTYPE>((a, b) => a.Id == b.Id // OR SOME OTHER COMPARISON RESOLVING TO BOOLEAN));
var results=list1.Except(list2,newgenericequalitycomparer((a,b)=>a.Id==b.Id//或其他解析为布尔值的比较);
下面是课程:

public class GenericEqualityComparer<T> : EqualityComparer<T>
{
    public Func<T, int> HashCodeFunc { get; set; }

    public Func<T, T, Boolean> EqualityFunc { get; set; }

    public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc)
    {
        EqualityFunc = equalityFunc;
        HashCodeFunc = null;
    }

    public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc, Func<T, int> hashCodeFunc) : this(equalityFunc)
    {
        HashCodeFunc = hashCodeFunc;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityFunc(x, y);
    }

    public override int GetHashCode(T obj)
    {
        if (HashCodeFunc == null)
        {
            return 1;
        }
        else
        {
            return HashCodeFunc(obj);
        }
    }
}
公共类GenericEqualityComparer:EqualityComparer
{
公共Func HashCodeFunc{get;set;}
公共Func EqualityFunc{get;set;}
公共通用资格比较程序(Func equalityFunc)
{
EqualityFunc=EqualityFunc;
HashCodeFunc=null;
}
public GenericEqualityComparer(Func equalityFunc,Func hashCodeFunc):此(equalityFunc)
{
HashCodeFunc=HashCodeFunc;
}
公共覆盖布尔等于(TX,TY)
{
返回等式func(x,y);
}
公共覆盖int GetHashCode(T obj)
{
if(HashCodeFunc==null)
{
返回1;
}
其他的
{
返回HashCodeFunc(obj);
}
}
}

我遇到了同样的问题,调试使我得到了与大多数人不同的答案。大多数人指出必须实现
GetHashCode()


然而,在我的例子中,LINQ的
SequenceEqual()
-
GetHashCode()
从未被调用。而且,尽管所涉及的每个对象都被类型化为特定类型
T
,但根本的问题是
SequenceEqual()
调用了
T.Equals(object other)
,我忘记了实现它,而不是调用预期的
T.Equals(T other)

尝试直接使用EqualityComparer.Default,看看不匹配是在该实现中还是在Linq方法中。然后打开Reflector并检查源代码,并在MSDN文档中添加注释?作为旁侧节点,行为不符合文档要求的内容被视为bug,因此我建议您将其作为Microsoft Connect提交。为了记录在案,我在过去已经修复了通过该渠道提交的较小的文档错误。至于选项1,我的理解是,如果我创建了自己的比较器来实现IEqualityComparer,那么我必须通过
public class GenericEqualityComparer<T> : EqualityComparer<T>
{
    public Func<T, int> HashCodeFunc { get; set; }

    public Func<T, T, Boolean> EqualityFunc { get; set; }

    public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc)
    {
        EqualityFunc = equalityFunc;
        HashCodeFunc = null;
    }

    public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc, Func<T, int> hashCodeFunc) : this(equalityFunc)
    {
        HashCodeFunc = hashCodeFunc;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityFunc(x, y);
    }

    public override int GetHashCode(T obj)
    {
        if (HashCodeFunc == null)
        {
            return 1;
        }
        else
        {
            return HashCodeFunc(obj);
        }
    }
}