Enumerable.Distinct - 您的自定义类必须使用哪些方法才能使其工作?

时间:2013-07-11 19:38:30

标签: c# .net

我已经实现了MSDN所说的必要的每个功能,加上一些额外的比较接口 - 似乎没什么用。以下是代码(针对LinqPad进行了优化)。 结果输出是4个项目,而不是我期望的2个项目。 请不要将工作作为答案发布 - 我想知道Distinct是如何工作的

void Main()
{
  List<NameClass> results = new List<NameClass>();
  results.Add(new NameClass("hello"));
  results.Add(new NameClass("hello"));
  results.Add(new NameClass("55"));
  results.Add(new NameClass("55"));
  results.Distinct().Dump();
}

// Define other methods and classes here

  public class NameClass : Object
    , IEquatable<NameClass>
    , IComparer<NameClass>
    , IComparable<NameClass>
    , IEqualityComparer<NameClass>
    , IEqualityComparer
    , IComparable
  {

    public NameClass(string name)
    {
      Name = name;
    }

    public string Name { get; private set; }

    public int Compare(NameClass x, NameClass y)
    {
      return String.Compare(x.Name, y.Name);
    }

    public int CompareTo(NameClass other)
    {
      return String.Compare(Name, other.Name);
    }

    public bool Equals(NameClass x, NameClass y)
    {
      return (0 == Compare(x, y));
    }

    public bool Equals(NameClass other)
    {
      return (0 == CompareTo(other));
    }

    public int GetHashCode(NameClass obj)
    {
      return obj.Name.GetHashCode();
    }

    public new int GetHashCode()
    {
      return Name.GetHashCode();
    }

    public new bool Equals(object a)
    {
      var x = a as NameClass;
      if (null == x) { return false; }
      return Equals(x);
    }

    public new bool Equals(object a, object b)
    {
      if (null == a && null == b) { return true;  }
      if (null == a && null != b) { return false; }
      if (null != a && null == b) { return false; }
      var x = a as NameClass;
      var y = b as NameClass; 
      if (null == x && null == y) { return true;  }
      if (null == x && null != y) { return false; }
      if (null != x && null == y) { return false; }
      return x.Equals(y);
    }

    public int GetHashCode(object obj)
    {
      if (null == obj) { return 0; }
      var x = obj as NameClass;
      if (null != x) { return x.GetHashCode(); }
      return obj.GetHashCode();
    }

    public int CompareTo(object obj)
    {
      if (obj == null) return 1;

      NameClass x = obj as NameClass;
      if (x == null) 
      {
        throw new ArgumentException("Object is not a NameClass");
      }
      return CompareTo(x);
    }
  }

5 个答案:

答案 0 :(得分:5)

Distinct如何运作:

至少没有Object.GetHashCode()的实现用于对象的初始比较:Distinct的基本版本首先比Object.GetHashCode比较(实际上放入字典),而不是哈希代码匹配Object.Equals

准确地说Enumerable.Distinct(this IEnumerable source)使用EqualityComparer<NameClass>.Default来最终检查是否相等(请注意,如果哈希码不匹配,则不会达到比较的那部分,这就是为什么你的样本不起作用)

  

默认的相等比较器Default用于比较实现IEquatable泛型接口的类型的值。

EqualityComparer.Default反过来实际上允许使用不带IEquatable<T>的课程直接回到Object.Equals

  

Default属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。否则,它返回一个EqualityComparer,它使用由T提供的Object.Equals和Object.GetHashCode的覆盖。

因此,要使基本Distinct正常工作,您只需要正确版本的Equals / GetHashCodeIEquatable是可选的,但必须与班级中GetHashCode的行为相匹配。


如何解决:

您的示例采用public new int GetHashCode()方法,可能应为public override int GetHashCode()Equals相同)。

请注意,public new int...并不意味着“覆盖”,而是“创建隐藏旧版本的方法的新版本”。它不影响通过指向父对象的方法调用方法的调用者。

我个人认为new很少用于定义方法。 Usecases for method hiding using new中介绍了一些有用的建议。

答案 1 :(得分:3)

您无需正确实施任何界面,只需GetHashCodeEquals方法:

public class NameClass
{
    public NameClass(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }

    public override bool Equals(object obj)
    {
        var other = obj as NameClass;
        return other != null && other.Name == this.Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

答案 2 :(得分:2)

Enumerable.Distinct<TSource> Method

  

它使用默认的相等比较器Default来比较值。

EqualityComparer.Default

  

Default属性检查类型 T 是否实现System.IEquatable&lt; T&gt;接口和(如果是)返回EqualityComparer&lt; T&gt;使用该实现。否则,它返回EqualityComparer&lt; T&gt;。它使用 T 提供的Object.Equals和Object.GetHashCode的覆盖。

IEquatable<T> Interface

  

如果您实现IEquatable&lt; T&gt;,您还应该覆盖Object.Equals(Object)GetHashCode的基类实现,以便它们的行为与IEquatable&lt; T&gt; .Equals方法的行为一致。< / p>

Overriding methods

  

覆盖修饰符是扩展或修改继承方法,属性,索引器或事件的抽象或虚拟实现所必需的。

所以你的代码应该是这样的:

public class NameClass : IEquatable<NameClass>
{
    public NameClass(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }

    // implement IEquatable<NameClass>
    public bool Equals(NameClass other)
    {
        return (other != null) && (Name == other.Name);
    }

    // override Object.Equals(Object)
    public override bool Equals(object obj)
    {
        return Equals(obj as NameClass);
    }

    // override Object.GetHashCode()
    public override GetHashCode()
    {
        return Name.GetHashCode();
    }
}

答案 3 :(得分:1)

所以,首先,Distinct将根据文档使用EqualityComparer<T>.Default来比较对象,如果没有提供自定义相等比较器(你没有提供)。

EqualityComparer<T>.Default,根据其文档,将查看对象是否实现IEquatable<T>,如果是,它将使用Equals的实现。

无论类型是否实现IEquatable<T>EqualityComparer<T>.Default 都将使用object.GetHashCode方法获取对象的代码。遗憾的是,IEquatable<T>并不会强制您覆盖对象的GetHashCode实现,在您的情况下,当您实现IEquatable<T>时,您的代码不会覆盖对象{ {1}}实施

由于此GetHashCode实际上正在为您的类型使用正确的Distinct方法,但它使用了错误的Equals方法。每当您对对象进行哈希处理时,该类型的GetHashCodeEquals实现都会出现不同步问题。发生的事情是,在任何基于散列的集合中,它将两个“相等”的对象发送到不同的桶,因此它们甚至都没有达到彼此调用GetHashCode方法的程度。如果你碰巧幸运并且有一个哈希集合并且这些对象巧合地被发送到同一个桶,那么,因为Equals方法是你想要的,它实际上会起作用,但发生这种情况的可能性是多少。 ..非常低。 (在这个特定的情况下,大约2/2147483647,或 9.3E-10

虽然您确实在Equals中提供了new GetHashCode方法,但它隐藏对象实现,而不是覆盖它。如果您将NameClass实施更改为使用GetHashCode而不是override,那么您的代码将有效。

答案 4 :(得分:0)

我刚刚意识到我弄乱了我的示例代码 - 我的类派生自DependencyObject,而不是Object。我无法覆盖GetHashCode或Equals函数,因为DependencyObject类是密封的。