HashSet如何比较元素是否相等?


127

我有一堂课是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.count3

  1. 为什么不HashSet尊重aCompareTo方法。
  2. HashSet拥有唯一对象列表的最佳方法是吗?

IEqualityComparer<T>在构造函数中添加实现,或在类中实现amsdn.microsoft.com/zh-CN/library/bb301504(v=vs.110).aspx
Jaider

Answers:


137

它使用一个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>在类型中实现,尤其是在它是值类型时。这些方法将由默认的相等比较器调用。

请注意,从顺序比较的角度来看,这些都不是什么-这很有意义,因为在某些情况下,您可以轻松指定相等性,但不能指定总顺序。Dictionary<TKey, TValue>基本上与都一样。

如果要使用排序而不是仅使用相等比较的集合,则应SortedSet<T>从.NET 4 使用-允许您指定一个IComparer<T>而不是IEqualityComparer<T>。这将使用IComparer<T>.Compare-将委派给,IComparable<T>.CompareTo或者IComparable.CompareTo如果您正在使用Comparer<T>.Default


7
+1另外值得注意@ tyriker的回答(海事组织应该在这里评论),它指出,要利用最简单的方式说IEqualityComparer<T>.GetHashCode/Equals()是落实EqualsGetHashCodeT本身(而你这样做,你想也实现了强类型对应:- bool IEquatable<T>.Equals(T other)
Ruben Bartelink

5
尽管此答案非常准确,但可能有些令人困惑,特别是对于新用户,因为它并未明确指出最简单的情况下EqualsGetHashCode它已经足够了-如@tyriker的答案中所述。
BartoszKP

Imo一旦实现IComparable(或IComparer就此而言),就不应要求您分别实现平等(而只是GetHashCode)。从某种意义上说,可比性接口应该继承于相等性接口。我确实了解具有两个单独的函数(在其中可以通过说某事是否相等来单独优化相等性)的性能好处,但是仍然..否则,当您指定实例在CompareTo功能上相同且框架不会考虑时,非常令人困惑那。
nawfal 2015年

@nawfal并非一切都有逻辑顺序。如果您要比较包含bool属性的两件事,那么必须编写类似于a.boolProp == b.boolProp ? 1 : 0或应该为a.boolProp == b.boolProp ? 0 : -1or的东西真是太糟糕了a.boolProp == b.boolProp ? 1 : -1。k!
Simon_Weaver

1
@Simon_Weaver是的。我确实想在我提出的假设功能中避免这种情况。
nawfal

77

以下是未回答的部分答案的澄清:您的对象类型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)使用该设备。


2
好点子。顺便说一句,正如我在@JonSkeet答案中的评论中提到的那样,您还应该实现bool IEquatable<T>.Equals(T other)一点效率提高,但更重要的是要获得清晰性。出于显而易见的原因,除了需要GetHashCode并排执行之外IEquatable<T>,IEquatable <T>的文档还提到出于一致性目的,您还应该覆盖object.Equalsfor一致性
Ruben Bartelink,

我尝试实现这一点。该ovveride getHashcode作品,但override bool equals得到的错误:没有找到方法来替代。任何想法?
Stefanvds 2014年

最后,我正在寻找的信息。谢谢。
Mauro Sampietro

根据我对上述答案的评论-在您的“而不是”情况下,您可以拥有public class a : IEqualityComparer<a> {,然后再选择new HashSet<a>(a)
HankCa '18

但是请参阅上面的Jon Skeets评论。
HankCa

9

HashSet使用EqualsGetHashCode()

CompareTo 用于有序集。

如果您想要唯一的对象,但是您不关心它们的迭代顺序,HashSet<T>则通常是最佳选择。


5

构造函数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的哈希值是多少?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.