[英]How does HashSet compare elements for equality?
我有一个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>
( EqualityComparer<T>.Default
除非您在构造时指定不同的一个)。
当您向集合添加元素时,它将使用IEqualityComparer<T>.GetHashCode
找到哈希码,并存储哈希码和元素(当然,在检查元素是否已经在集合中之后)。
要查找元素,它将首先使用IEqualityComparer<T>.GetHashCode
来查找哈希码,然后对于具有相同哈希码的所有元素,它将使用IEqualityComparer<T>.Equals
来比较实际的相等性。
这意味着您有两种选择:
IEqualityComparer<T>
传递给构造函数。 如果您无法修改T
本身,或者您需要非默认的相等关系(例如“具有负用户ID的所有用户被视为相等”),则这是最佳选项。 这几乎从未在类型本身上实现(即Foo
不实现IEqualityComparer<Foo>
),而是在单独的类型中仅用于比较。 GetHashCode
和Equals(object)
在类型本身中实现相等性。 理想情况下, IEquatable<T>
在类型中实现IEquatable<T>
,特别是如果它是值类型。 这些方法将由默认的相等比较器调用。 请注意,对于有序比较而言,这一切都没有 - 这是有道理的,因为在某些情况下您可以轻松指定相等但不是总排序。 这基本上与Dictionary<TKey, TValue>
完全相同。
如果你想要一个使用排序而不仅仅是相等比较的集合,你应该使用.NET 4中的SortedSet<T>
- 它允许你指定IComparer<T>
而不是IEqualityComparer<T>
。 这将使用IComparer<T>.Compare
- 如果您使用的是Comparer<T>.Default
,它将委托给IComparable<T>.CompareTo
或IComparable.CompareTo
。
以下是对未提及的答案的一部分的澄清: 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)
。
HashSet
使用Equals
和GetHashCode()
。
CompareTo
适用于有序集。
如果您想要唯一对象,但不关心它们的迭代顺序, HashSet<T>
通常是最佳选择。
构造函数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");
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.