Distinct()返回具有用户定义类型的重复项

时间:2010-11-29 23:05:17

标签: c# linq

我正在尝试编写一个Linq查询,该查询返回一个对象数组,其构造函数中包含唯一值。对于整数类型,Distinct只返回每个值的一个副本,但是当我尝试创建对象列表时,事情就会崩溃。我怀疑这是我班级的相等运算符的问题,但是当我设置断点时,它从未被击中。

在子表达式中过滤掉重复的int可以解决问题,并且还可以避免构造将立即丢弃的对象,但我很好奇为什么这个版本不起作用。

更新:晚上11:04 有几个人指出MyType不会覆盖GetHashCode()。我担心这个例子过于简单了。原始的MyType确实实现了它。我在下面添加了它,仅修改为在返回它之前将哈希码放入临时变量中。

通过调试器,我看到GetHashCode的所有五次调用都返回不同的值。由于MyType只从Object继承,因此可能与Object表现出相同的行为。

我是否正确然后得出结论,哈希应该基于值的内容?这是我第一次尝试覆盖运营商,当时看起来GetHashCode似乎并不特别花哨。 (这是我的第一次平等检查似乎无法正常工作。)

class Program
{
    static void Main(string[] args)
    {
        int[] list = { 1, 3, 4, 4, 5 };
        int[] list2 =
            (from value in list
             select value).Distinct().ToArray();    // One copy of each value.
        MyType[] distinct =
            (from value in list
             select new MyType(value)).Distinct().ToArray(); // Two objects created with 4.

        Array.ForEach(distinct, value => Console.WriteLine(value));
    }
}

class MyType
{
    public int Value { get; private set; }

    public MyType(int arg)
    {
        Value = arg;
    }

    public override int GetHashCode()
    {
        int retval = base.GetHashCode();
        return retval;
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        MyType rhs = obj as MyType;
        if ((Object)rhs == null)
            return false;

        return this == rhs;
    }

    public static bool operator ==(MyType lhs, MyType rhs)
    {
        bool result;

        if ((Object)lhs != null && (Object)rhs != null)
            result = lhs.Value == rhs.Value;
        else
            result = (Object)lhs == (Object)rhs;

        return result;
    }

    public static bool operator !=(MyType lhs, MyType rhs)
    {
        return !(lhs == rhs);
    }
}

7 个答案:

答案 0 :(得分:8)

您需要在类中重写GetHashCode()。 GetHashCode必须与Equals重载一起实现。代码在调用Equals之前检查哈希码是否相等是很常见的。这就是为什么你的Equals实现没有被调用。

答案 1 :(得分:2)

您的怀疑是正确的,它是当前只检查对象引用的相等。即使你的实现没有做任何额外的事情,改为:

public override bool Equals(object obj)
{
    if (obj == null)
        return false;

    MyType rhs = obj as MyType;
    if ((Object)rhs == null)
        return false;

    return this.Value == rhs.Value;
}

答案 2 :(得分:2)

在你的平等方法中,你仍在测试引用相等性,而不是语义相等性,例如在这一行:

result = (Object)lhs == (Object)rhs

您只是比较两个对象引用,即使它们拥有完全相同的数据,它们仍然不是同一个对象。相反,您的相等性测试需要比较对象的一个​​或多个属性。例如,如果您的对象具有ID属性,并且具有相同ID的对象应被视为在语义上等效,那么您可以这样做:

result = lhs.ID == rhs.ID

请注意,重写Equals()意味着你还应该覆盖GetHashCode(),这是另一个鱼的水壶,并且可能很难正确完成。

答案 3 :(得分:1)

您需要实现GetHashCode()。

答案 4 :(得分:1)

似乎可以更加优雅地实现简单的Distinct操作,如下所示:

var distinct = items.GroupBy(x => x.ID).Select(x => x.First());

其中,ID是确定两个对象在语义上是否等效的属性。从这里的混乱(包括我自己)来看,Distinct()的默认实现似乎有点复杂。

答案 5 :(得分:0)

我认为MyType需要实现IEquatable才能实现此目的。

答案 6 :(得分:0)

其他答案几乎涵盖了您需要正确实现Equals和GetHashCode这一事实,但作为旁注,您可能有兴趣知道匿名类型会自动实现这些值:

var distinct =
        (from value in list
         select new {Value = value}).Distinct().ToArray();

因此,无需定义此类,您将自动获得您正在寻找的Equals和GetHashCode行为。很酷,嗯?