LINQ和Distinct,实现Equals和GetHashCode函数

7

我正在尝试使它工作,但似乎不知道为什么它不能工作。

演示代码:

namespace ConsoleApplication1
{
class Program
{
    static void Main(string[] args)
    {
        var myVar = new List<parent >();
        myVar.Add(new parent() { id = "id1", blah1 = "blah1", c1 = new child() { blah2 = "blah2", blah3 = "blah3" } });
        myVar.Add(new parent() { id = "id1", blah1 = "blah1", c1 = new child() { blah2 = "blah2", blah3 = "blah3" } });

        var test = myVar.Distinct();

        Console.ReadKey();

    }
}


public class parent : IEquatable<parent>
{
    public String id { get;set;}
    public String blah1 { get; set; }
    public child c1 { get; set; }

    public override int GetHashCode()
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + id.GetHashCode();
            hash = hash * 23 + blah1.GetHashCode();
            hash = hash * 23 + (c1 == null ? 0 : c1.GetHashCode());
            return hash;
        }
    }

    public bool Equals(parent other)
    {
        return object.Equals(id, other.id) &&
            object.Equals(blah1, other.blah1) &&
            object.Equals(c1, other.c1);
    }

}

public class child : IEquatable<child>
{
    public String blah2 { get; set; }
    public String blah3 { get; set; }

    public override int GetHashCode()
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + blah2.GetHashCode();
            hash = hash * 23 + blah3.GetHashCode();
            return hash;
        }
    }

    public bool Equals(child other)
    {
        return object.Equals(blah2, other.blah2) &&
            object.Equals(blah3, other.blah3);
    }

}
}

有人能发现我的错误吗?


1
你遇到了什么错误?预期的行为是什么?执行你的代码时,当按下键后,它只是显示空白并退出。 - jdmichal
@jdmichal,在 console.readkey 上放一个断点,看一下 test 变量,它应该是 count = 1 而不是 2。 - Fredou
@jdmichal 从描述中可以安全地假设期望是Distinct()只返回一个项目,但实际上它并没有这样做。 - BrokenGlass
4个回答

5
你需要重写Equals(object)方法:
public override bool Equals(object obj) {
    return Equals(obj as parent);
}
object.Equals 方法(与 EqualityComparer<T>.Default 不同)不使用 IEquatable 接口。 因此,当您编写 object.Equals(c1, other.c1) 时,它不会调用您的 Child.Equals(Child) 方法。
对于 parent 您并不一定需要这样做,但是您确实应该这样做。

错误:'ConsoleApplication1.parent.Equals(ConsoleApplication1.parent)':没有找到适合重写的方法。 - Fredou
@Fredou:我忘记添加返回类型了。 - SLaks
我有相反的情况:我已经重写了GetHashCode和Equals(实际上不应该只重写其中一个!),但没有实现IEquatable。当我使用Distinct()扩展时,LINQ甚至不会调用这些方法。这是一种我们按设计意图应该重复自己的情况吗? - The Dag
1
查看了MSDN,结果发现必须同时进行以下两个步骤才能比较自定义数据类型:“要比较自定义数据类型,您需要实现此接口[IEquatable<T>]并为该类型提供自己的GetHashCode和Equals方法。”我很烦恼微软一直在重复和三番五次地做同样的事情,很难知道您的类型是否始终有效!IComparable、IComparer、IEquatable、IEqualityComparer、Equals和GetHashCode——这就足够了吗?! - The Dag
可能值得指出的一件事是,如果您不覆盖GetHashCode,那么Distinct()将无法工作,或者不会按预期工作。 - Chris S
显示剩余2条评论

3
要么按照SLaks的建议去做,要么在你的父类中使用EqualityComparer<child>.Default来使用你的IEquatable<child>实现。
  public bool Equals(parent other)
  {
   return object.Equals(id, other.id) &&
    object.Equals(blah1, other.blah1) &&
    EqualityComparer<child>.Default.Equals(c1, other.c1);
 }

现在我需要知道什么是最佳实践,是实现这个还是SLaks的方式。 - Fredou
1
真的应该重写Equals(object)方法。否则,你很可能会遇到其他令人不愉快的意外。 - SLaks
应该重写Equals(object)方法:同意。 - ulrichb
我希望我能接受两个答案,因为这个也可以工作,SLaks的更加牢靠。 - Fredou
1
在我看来,父对象和子对象都可能是“实体”。为什么不从一个共同的基类派生它们,该基类仅对标识(ID属性)、相等性和比较进行建模?然后,当您需要第三个或第二十二个类型执行相同操作时,您不会得到23份代码副本。拥有一种多态的方式来识别对象也可以用于其他目的,例如O/R映射、缓存、仪器/跟踪等。 - The Dag

1
这里有几件事情需要正确处理。如果我要在像GetHashCode,覆盖==或IEquatable这样的类中实现任何等式方面,我总是使用以下模式。
  1. 覆盖Equals
  2. 覆盖GetHashCode
  3. 实现IEquatable<T>,这意味着实现Equals(T)
  4. 实现!=
  5. 实现==
所以,如果我有一个名为ExpiryMonth的类,其中包含Year和Month属性,那么实现如下。现在很容易为其他类型的类进行适应。
我已经基于其他stackoverflow答案制定了这个模式,它们都值得赞扬,但我没有一路追踪。
通过始终一起实现所有这些元素,可以确保在各种上下文中(包括字典和Linq操作)进行适当的相等操作。
    public static bool operator !=(ExpiryMonth em1, ExpiryMonth em2)
    {
        if (((object)em1) == null || ((object)em2) == null)
        {
            return !Object.Equals(em1, em2);
        }
        else
        {
            return !(em1.Equals(em2));
        }
    }
    public static bool operator ==(ExpiryMonth em1, ExpiryMonth em2)
    {
        if (((object)em1) == null || ((object)em2) == null)
        {
            return Object.Equals(em1, em2);
        }
        else
        {
            return em1.Equals(em2);
        }
    }
    public bool Equals(ExpiryMonth other)
    {
        if (other == null) { return false; }
        return Year == other.Year && Month == other.Month;
    }
    public override bool Equals(object obj)
    {
        if (obj == null) { return false; }
        ExpiryMonth em = obj as ExpiryMonth;
        if (em == null) { return false; }
        else { return Equals(em); }
    }
    public override int GetHashCode()
    {
        unchecked // Overflow is not a problem
        {
            var result = 17;
            result = (result * 397) + Year.GetHashCode();
            result = (result * 397) + Month.GetHashCode();
            return result;
        }
    }

0

当添加计算哈希时,您可能想尝试类似以下的内容:

hash ^= id.GetHashCode();

不确定是否这是导致您问题的原因。


我相信他的哈希是没问题的 - 而且无论如何,如果它有问题,那只会导致性能不佳,而不是错误的输出(只要哈希是确定性的,在这种情况下显然是这样)。而且性能问题在只有两个项目的情况下肯定不会引起注意。 :) - The Dag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接