1

我已经实现了 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);
    }
  }
4

5 回答 5

5

工作原理Distinct

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

准确地说Enumerable.Distinct(this IEnumerable source)用于EqualityComparer<NameClass>.Default最终检查是否相等(请注意,如果哈希码不匹配,它将无法到达比较的那部分,这就是您的示例不起作用的原因)。

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

EqualityComparer.Default反过来实际上允许使用类而不IEquatable<T>直接回退到Object.Equals

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

因此,对于基本Distinct工作,您只需要正确版本的Equals/ GetHashCodeIEquatable是可选的,但必须匹配GetHashCode类中的行为。


怎么修:

您的样本有public new int GetHashCode()方法,可能应该是public override int GetHashCode()(相同的Equals)。

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

我个人认为new应该很少在定义方法时使用。Usecases中介绍了一些有用的建议,用于使用 new 隐藏方法

于 2013-07-11T19:44:47.303 回答
3

您不必实现任何接口,只需GetHashCode正确实现Equals方法即可:

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();
    }
}
于 2013-07-11T19:47:12.780 回答
2

Enumerable.Distinct<TSource> 方法

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

EqualityComparer.Default

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

IEquatable<T> 接口

如果您实现 IEquatable<T>,您还应该重写Object.Equals(Object)GetHashCode的基类实现,以便它们的行为与 IEquatable<T>.Equals 方法的行为一致。

覆盖方法

需要override修饰符来扩展或修改继承的方法、属性、索引器或事件的抽象或虚拟实现。

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

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();
    }
}
于 2013-07-11T19:54:05.093 回答
1

因此,首先Distinct,根据它的文档,EqualityComparer<T>.Default如果没有提供自定义相等比较器(您没有提供),将用于比较对象。

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

不管类型是否实现IEquatable<T>EqualityComparer<T>.Default 都会使用object.GetHashCode方法获取对象的has代码IEquatable<T>,不幸的是,不会强迫您也覆盖对象的GetHashCode实现,并且在您的情况下,当您实现IEquatable<T>时,您的代码不会覆盖对象的GetHashCode实现

因此,这Distinct实际上是Equals为您的类型使用了正确的方法,但它使用了错误的GetHashCode方法。每当您对对象进行哈希处理并且该类型具有不同步EqualsGetHashCode实现时,就会出现问题。Equals发生的事情是,在任何基于散列的集合中,它会将两个“相等”的对象发送到不同的存储桶,因此它们甚至永远不会到达相互调用它们的方法的地步。如果您碰巧有一个哈希集合并且对象恰好被发送到同一个存储桶,那么,由于该Equals方法是您想要的,它实际上会起作用,但是发生这种情况的几率......非常低。(在这种特定情况下,大约 2/2147483647,或 9.3e-10。

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

于 2013-07-11T20:06:01.153 回答
0

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

于 2013-07-11T20:05:26.777 回答