C# Enumerable.Distinct-自定义类必须实现哪些方法才能工作?
我已经实现了MSDN所说的每一个必要的功能,加上一些额外的比较接口——似乎什么都不起作用。下面是为LinqPad优化的代码。 结果输出是全部4项,而不是我期望的2项。 请不要将变通方法作为答案发布-我想知道Distinct是如何工作的C# Enumerable.Distinct-自定义类必须实现哪些方法才能工作?,c#,.net,C#,.net,我已经实现了MSDN所说的每一个必要的功能,加上一些额外的比较接口——似乎什么都不起作用。下面是为LinqPad优化的代码。 结果输出是全部4项,而不是我期望的2项。 请不要将变通方法作为答案发布-我想知道Distinct是如何工作的 void Main() { List<NameClass> results = new List<NameClass>(); results.Add(new NameClass("hello")); results.Add(ne
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);
}
}
Distinct的工作原理:
至少没有用于对象初始比较的Object.GetHashCode的实现:Distinct compares的基本版本实际上首先按Object.GetHashCode放入字典,而不是哈希代码按Object.Equals匹配
确切地说,使用EqualityComparer.Default最终检查相等性注意,如果哈希代码不匹配,它将不会到达比较的那个部分,这就是您的示例无法工作的原因
默认相等比较器default用于比较实现IEquatable泛型接口的类型的值
反过来,实际上允许使用类而不必直接返回到对象。等于:
默认属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。否则,它将返回一个EqualityComparer,使用T提供的Object.Equals和Object.GetHashCode的重写
因此,要使basicdistinct工作,您只需要Equals/GetHashCode的正确版本。IEquatable是可选的,但必须匹配类中GetHashCode的行为
如何修复:
您的示例有一个公共的新int-GetHashCode方法,该方法很可能应该是public,它可以覆盖int-GetHashCode,与Equals相同
请注意,公共新国际。。。并不意味着重写,而是创建隐藏旧方法的新版本。它不会影响通过指向父对象的指针调用方法的调用方
我个人认为,在定义方法时,应该很少使用新方法。一些有用的建议见。如何使用Distinct:
至少没有用于对象初始比较的Object.GetHashCode的实现:Distinct compares的基本版本实际上首先按Object.GetHashCode放入字典,而不是哈希代码按Object.Equals匹配
确切地说,使用EqualityComparer.Default最终检查相等性注意,如果哈希代码不匹配,它将不会到达比较的那个部分,这就是您的示例无法工作的原因
默认相等比较器default用于比较实现IEquatable泛型接口的类型的值
反过来,实际上允许使用类而不必直接返回到对象。等于:
默认属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。否则,它将返回一个EqualityComparer,使用T提供的Object.Equals和Object.GetHashCode的重写
因此,要使basicdistinct工作,您只需要Equals/GetHashCode的正确版本。IEquatable是可选的,但必须匹配类中GetHashCode的行为
如何修复:
您的示例有一个公共的新int-GetHashCode方法,该方法很可能应该是public,它可以覆盖int-GetHashCode,与Equals相同
请注意,公共新国际。。。并不意味着重写,而是创建隐藏旧方法的新版本。它不会影响通过指向父对象的指针调用方法的调用方
我个人认为,在定义方法时,应该很少使用新方法。本节介绍了一些有用的建议。您不必实现任何接口,只需正确地获取hashcode和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();
}
}
您不必实现任何接口,只需正确地获取hashcode和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();
}
}
:
它使用默认的相等比较器来比较值
:
属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。否则,它将返回一个EqualityComparer,使用T提供的Object.Equals和Object.GetHashCode的重写
:
如果实现IEquatable,还应该重写和的基类实现,以便它们的行为与IEquatable.Equals方法的行为一致
:
重写修饰符是扩展或修改继承的方法、属性、索引器或事件的抽象或虚拟实现所必需的
因此,您的代码应该如下所示:
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();
}
}
:
它使用默认的相等比较器来比较值
:
属性检查类型T是否实现System.IEquatable接口,如果是,则返回使用该实现的EqualityComparer。否则,它将返回使用Obj覆盖的EqualityComparer
ect.Equals和Object.GetHashCode由T提供
:
如果实现IEquatable,还应该重写和的基类实现,以便它们的行为与IEquatable.Equals方法的行为一致
:
重写修饰符是扩展或修改继承的方法、属性、索引器或事件的抽象或虚拟实现所必需的
因此,您的代码应该如下所示:
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();
}
}
我刚刚意识到我弄乱了我的示例代码——我的类是从DependencyObject派生的,而不是Object。我无法重写W GetHashCode或Equals函数,因为DependencyObject类是密封的 我刚刚意识到我把示例代码弄糟了——我的类是从DependencyObject而不是Object派生的。我无法重写W GetHashCode或Equals函数,因为DependencyObject类是密封的 因此,首先,Distinct将根据其文档使用EqualityComparer.Default来比较对象,如果没有提供自定义EqualityComparer,则您没有提供 默认情况下,根据其文档,将查看对象是否实现了IEquatable,如果实现了IEquatable,则将使用Equals的实现 无论类型是否实现IEquatable,EqualityComparer.Default都将使用object.GetHashCode方法获取对象的has代码。不幸的是,IEquatable并不强制您也重写对象的GetHashCode实现,在您的情况下,虽然您确实实现了IEquatable,但您的代码并不重写对象的GetHashCode实现 因此,Distinct实际上为您的类型使用了正确的Equals方法,但它使用了错误的GetHashCode方法。当您对对象进行散列时,如果该类型有一个Equals和GetHashCode实现,则会出现不同步的问题。所发生的事情是,在任何基于散列的集合中,它都会将两个相等的对象发送到不同的bucket,因此它们甚至不会到达各自的Equals方法被调用的点。如果你碰巧运气好,有一个散列集合,并且对象恰好被发送到同一个bucket,那么,由于Equals方法是你想要的,所以它实际上会工作,但是发生这种情况的几率……非常低。在此特定情况下,约2/2147483647,或 9.3e-10 虽然您确实在NameClass中提供了一个新的GetHashCode方法,但它隐藏了对象实现,而不是覆盖它。如果您将GetHashCode实现更改为使用override而不是new,那么您的代码将正常工作。因此,首先,Distinct将根据其文档使用EqualityComparer。如果未提供自定义equality comparer,则默认情况下比较对象。如果您未提供任何自定义equality comparer,则默认情况下比较对象 默认情况下,根据其文档,将查看对象是否实现了IEquatable,如果实现了IEquatable,则将使用Equals的实现 无论类型是否实现IEquatable,EqualityComparer.Default都将使用object.GetHashCode方法获取对象的has代码。不幸的是,IEquatable并不强制您也重写对象的GetHashCode实现,在您的情况下,虽然您确实实现了IEquatable,但您的代码并不重写对象的GetHashCode实现 因此,Distinct实际上为您的类型使用了正确的Equals方法,但它使用了错误的GetHashCode方法。当您对对象进行散列时,如果该类型有一个Equals和GetHashCode实现,则会出现不同步的问题。所发生的事情是,在任何基于散列的集合中,它都会将两个相等的对象发送到不同的bucket,因此它们甚至不会到达各自的Equals方法被调用的点。如果你碰巧运气好,有一个散列集合,并且对象恰好被发送到同一个bucket,那么,由于Equals方法是你想要的,所以它实际上会工作,但是发生这种情况的几率……非常低。在此特定情况下,约2/2147483647,或 9.3e-10
虽然您确实在NameClass中提供了一个新的GetHashCode方法,但它隐藏了对象实现,而不是覆盖它。如果将GetHashCode实现更改为使用override而不是new,则代码将正常工作。此公共新int GetHashCode行的用途是什么???它将覆盖System.Object.GetHashCode函数,根据MSDN docs.new中的指定,它不会覆盖但会隐藏基类中的成员。这个公共的新int-GetHashCode行是用于什么的???它覆盖System.Object.GetHashCode func,正如MSDN docs.new中指定的那样,它不重写,而是隐藏基类中的成员。我讨厌override和new-因此需要将两个函数标记为override,但其中一个函数应将new public override int GetHashCode{return Name.GetHashCode;}public override bool Equalsobject a标记为new public override{var x=
a作为名称类;如果null==x{return false;}返回Equalsx;}public new bool equals object a,object b{}MSDN文档非常容易引起误解-它表明,只要类实现了IEquatable,就可以使用该接口。技术上不同的使用EqualityComparer.Default@Drew,它确实被使用了,文档中没有任何误导。谢谢你在这里解释操作顺序。首先调用GetHashCode并将其作为备份。我一直在试图弄清楚为什么没有调用Equals,这是20篇文章中第一篇调用这一点的文章。我讨厌override和new-所以两个函数需要标记为override,但一个函数应该标记为new public override int GetHashCode{return Name.GetHashCode;}public override bool equalobject a{var x=a作为NameClass;如果null==x{return false;}return Equalsx;}public new bool equalobject a,object b{}MSDN文档非常误导-它表明,只要类实现IEquatable,然后将使用该接口。技术上不同的使用EqualityComparer.Default@Drew,它确实被使用了,文档中没有任何误导。谢谢你在这里解释操作顺序。首先调用GetHashCode并将其作为备份。我一直在想为什么没有调用Equals,这是20篇文章中第一篇调用这一点的文章。这是最干净的代码,但请注意,从未使用过IEquatable接口-在其中放置断点,函数不会被命中。@Drew:IEquatable.Equals用于正确重写GetHashCode。这是最干净的代码,但请注意,从未使用过IEquatable接口-在其中放置断点,函数将不会被命中。@Drew:IEquatable.Equals用于正确重写GetHashCode。如果DependencyObject被密封,那么如何从中派生?GetHashCode和Equals函数被密封,不是整个类。它非常清楚地说明了为什么不应该在DependencyObject中重写Equals和GetHashCode。@Drew假设不应该在NameClass上本机定义值相等,您可以定义一个实现IEqualityComparer的单独类,并将其传递给Distinct,以便仍能进行值比较。如果DependencyObject是密封的,那么如何从中派生?GetHashCode和Equals函数是密封的,并非整个类都清楚地说明了为什么不应在DependencyObject中重写Equals和GetHashCode。@Drew鉴于不应在NameClass上本机定义值相等,您可以定义一个实现IEqualityComparer的单独类,并将其传递给Distinct,以便仍能进行值比较。