HashSet 如何比较元素是不是相等?
Posted
技术标签:
【中文标题】HashSet 如何比较元素是不是相等?【英文标题】:How does HashSet compare elements for equality?HashSet 如何比较元素是否相等? 【发布时间】:2012-02-15 15:27:03 【问题描述】:我有一个班级是IComparable
:
public class a : IComparable
public int Id get; set;
public string Name get; set;
public a(int id)
this.Id = id;
public int CompareTo(object obj)
return this.Id.CompareTo(((a)obj).Id);
当我将此类的对象列表添加到哈希集时:
a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(a1);
一切都很好,ha.count
是 2
,但是:
a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(new a(1));
现在ha.count
是3
。
-
为什么
HashSet
不尊重a
的CompareTo
方法。
HashSet
是拥有唯一对象列表的最佳方式吗?
【问题讨论】:
在构造函数中添加IEqualityComparer<T>
的实现或者在a
类中实现。 msdn.microsoft.com/en-us/library/bb301504(v=vs.110).aspx
【参考方案1】:
它使用IEqualityComparer<T>
(EqualityComparer<T>.Default
,除非您在构造时指定不同的)。
当你将一个元素添加到集合中时,它会使用IEqualityComparer<T>.GetHashCode
找到哈希码,并将哈希码和元素都存储起来(当然是在检查元素是否已经在集合中之后)。
要查找一个元素,它将首先使用IEqualityComparer<T>.GetHashCode
来查找哈希码,然后对于具有相同哈希码的所有元素,它将使用IEqualityComparer<T>.Equals
来比较实际是否相等。
这意味着您有两个选择:
将自定义IEqualityComparer<T>
传递给构造函数。如果您不能修改 T
本身,或者您想要一个非默认的相等关系(例如“所有具有负用户 ID 的用户都被认为是相等的”),这是最好的选择。这几乎从未在类型本身上实现(即Foo
没有实现IEqualityComparer<Foo>
),而是在一个单独的类型中实现,仅用于比较。
通过覆盖GetHashCode
和Equals(object)
在类型本身中实现相等。理想情况下,也要在类型中实现IEquatable<T>
,尤其是在它是值类型的情况下。这些方法将由默认的相等比较器调用。
请注意,就 ordered 比较而言,这一切都不是 - 这是有道理的,因为在某些情况下,您可以轻松地指定相等但不能指定全序。这和Dictionary<TKey, TValue>
基本一样。
如果您想要一个使用 ordering 而不仅仅是相等比较的集合,您应该使用 .NET 4 中的 SortedSet<T>
- 它允许您指定 IComparer<T>
而不是 IEqualityComparer<T>
.这将使用IComparer<T>.Compare
- 如果您使用的是Comparer<T>.Default
,它将委托给IComparable<T>.CompareTo
或IComparable.CompareTo
。
【讨论】:
+1 另请注意@tyriker 的回答(IMO 应在此处发表评论)指出利用IEqualityComparer<T>.GetHashCode/Equals()
的最简单方法是在@987654347 上实施Equals
和GetHashCode
@ 本身(当你这样做的时候,你也会实现强类型的对应物:-bool IEquatable<T>.Equals(T other)
)
虽然这个答案非常准确,但可能有点令人困惑,尤其是对于新用户来说,因为它没有明确说明对于最简单的情况覆盖 Equals
和 GetHashCode
就足够了 - 正如@tyriker's 中所述回答。
Imo 一旦你实现了IComparable
(或IComparer
),你不应该被要求单独实现相等(而只是GetHashCode
)。从某种意义上说,可比接口应该继承自相等接口。我确实理解拥有两个独立函数的性能优势(您可以通过说出某事物是否相等来分别优化相等性)但仍然......否则当您在CompareTo
函数中指定实例何时相等时非常令人困惑,并且框架不会考虑这一点。
@nawfal 并非所有事物都有逻辑顺序。如果您要比较两个包含 bool 属性的东西,那么必须编写类似 a.boolProp == b.boolProp ? 1 : 0
或者应该是 a.boolProp == b.boolProp ? 0 : -1
或 a.boolProp == b.boolProp ? 1 : -1
之类的东西真是太糟糕了。哎呀!
@Simon_Weaver 是。我确实想在我提出的假设特征中以某种方式避免它。【参考方案2】:
这里澄清了部分未说明的答案:HashSet<T>
的对象类型不必实现IEqualityComparer<T>
,而只需覆盖Object.GetHashCode()
和Object.Equals(Object obj)
。
而不是这个:
public class a : IEqualityComparer<a>
public int GetHashCode(a obj) /* Implementation */
public bool Equals(a obj1, a obj2) /* Implementation */
你这样做:
public class a
public override int GetHashCode() /* Implementation */
public override bool Equals(object obj) /* Implementation */
这很微妙,但这让我在一天的大部分时间里试图让 HashSet 按预期方式运行。就像其他人所说的那样,HashSet<a>
最终会在使用该集合时根据需要调用a.GetHashCode()
和a.Equals(obj)
。
【讨论】:
好点。顺便说一句,正如我对@JonSkeet 回答的评论中提到的那样,您还应该实施bool IEquatable<T>.Equals(T other)
以获得轻微的效率提升,但更重要的是提高清晰度。出于明显的原因,除了需要在 IEquatable<T>
旁边实现 GetHashCode
之外,IEquatableobject.Equals
以保持一致性ovveride getHashcode
有效,但 override bool equals
得到错误:找不到可覆盖的方法。有什么想法吗?
终于找到了我想要的信息。谢谢。
来自我对上述答案的 cmets - 在您的“代替”的情况下,您可以拥有 public class a : IEqualityComparer<a>
,然后是 new HashSet<a>(a)
。
但请参阅上面的 Jon Skeets cmets。【参考方案3】:
HashSet
使用Equals
和GetHashCode()
。
CompareTo
用于有序集。
如果您想要唯一的对象,但不关心它们的迭代顺序,HashSet<T>
通常是最佳选择。
【讨论】:
【参考方案4】:构造函数 HashSet 接收实现 IEqualityComparer 以添加新对象的对象。 如果你想在 HashSet 中使用方法,你需要覆盖 Equals,GetHashCode
namespace HashSet
public class Employe
public Employe()
public string Name get; set;
public override string ToString()
return Name;
public override bool Equals(object obj)
return this.Name.Equals(((Employe)obj).Name);
public override int GetHashCode()
return this.Name.GetHashCode();
class EmployeComparer : IEqualityComparer<Employe>
public bool Equals(Employe x, Employe y)
return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower());
public int GetHashCode(Employe obj)
return obj.Name.GetHashCode();
class Program
static void Main(string[] args)
HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer());
hashSet.Add(new Employe() Name = "Nik" );
hashSet.Add(new Employe() Name = "Rob" );
hashSet.Add(new Employe() Name = "Joe" );
Display(hashSet);
hashSet.Add(new Employe() Name = "Rob" );
Display(hashSet);
HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer());
hashSetB.Add(new Employe() Name = "Max" );
hashSetB.Add(new Employe() Name = "Solomon" );
hashSetB.Add(new Employe() Name = "Werter" );
hashSetB.Add(new Employe() Name = "Rob" );
Display(hashSetB);
var union = hashSet.Union<Employe>(hashSetB).ToList();
Display(union);
var inter = hashSet.Intersect<Employe>(hashSetB).ToList();
Display(inter);
var except = hashSet.Except<Employe>(hashSetB).ToList();
Display(except);
Console.ReadKey();
static void Display(HashSet<Employe> hashSet)
if (hashSet.Count == 0)
Console.Write("Collection is Empty");
return;
foreach (var item in hashSet)
Console.Write("0, ", item);
Console.Write("\n");
static void Display(List<Employe> list)
if (list.Count == 0)
Console.WriteLine("Collection is Empty");
return;
foreach (var item in list)
Console.Write("0, ", item);
Console.Write("\n");
【讨论】:
如果名称为空怎么办? null 的哈希值是多少?【参考方案5】:我来这里寻找答案,但发现所有答案信息太多或不够,所以这是我的答案......
由于您已经创建了一个自定义类,您需要实现GetHashCode
和Equals
。在这个例子中,我将使用一个类Student
而不是a
,因为它更容易遵循并且不违反任何命名约定。 以下是实现的样子:
public override bool Equals(object obj)
return obj is Student student && Id == student.Id;
public override int GetHashCode()
return HashCode.Combine(Id);
我偶然发现了this article from Microsoft,如果您使用的是 Visual Studio,它提供了一种非常简单的方法来实现这些。如果它对其他人有帮助,以下是使用 Visual Studio 在 HashSet 中使用自定义数据类型的完整步骤:
给定一个类Student
,它有两个简单的属性和一个初始化器
public class Student
public int Id get; set;
public string Name get; set;
public Student(int id)
this.Id = id;
要实现 IComparable,请像这样添加 : IComparable<Student>
:
public class Student : IComparable<Student>
您会看到一条红色波浪线出现一条错误消息,指出您的类没有实现 IComparable。单击建议或按 Alt+Enter 并使用建议来实现它。
您将看到生成的方法。然后,您可以编写自己的实现,如下所示:
public int CompareTo(Student student)
return this.Id.CompareTo(student.Id);
在上面的实现中,只比较了 Id 属性,忽略了 name。接下来右键单击您的代码并选择Quick actions and refactorings,然后Generate Equals and GetHashCode
将弹出一个窗口,您可以在其中选择用于散列的属性,如果您愿意,甚至可以实现 IEquitable:
这是生成的代码:
public class Student : IComparable<Student>, IEquatable<Student>
...
public override bool Equals(object obj)
return Equals(obj as Student);
public bool Equals(Student other)
return other != null && Id == other.Id;
public override int GetHashCode()
return HashCode.Combine(Id);
现在,如果您尝试添加如下所示的重复项目,它将被跳过:
static void Main(string[] args)
Student s1 = new Student(1);
Student s2 = new Student(2);
HashSet<Student> hs = new HashSet<Student>();
hs.Add(s1);
hs.Add(s2);
hs.Add(new Student(1)); //will be skipped
hs.Add(new Student(3));
您现在可以像这样使用.Contains
:
for (int i = 0; i <= 4; i++)
if (hs.Contains(new Student(i)))
Console.WriteLine($@"Set contains student with Id i");
else
Console.WriteLine($@"Set does NOT contain a student with Id i");
输出:
【讨论】:
以上是关于HashSet 如何比较元素是不是相等?的主要内容,如果未能解决你的问题,请参考以下文章