I have a class that is 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);
}
}
When I add a list of object of this class to a hash set:
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);
Everything is fine and ha.count
is 2
, but:
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));
Now ha.count
is 3
.
- Why doesn't
HashSet
respecta
'sCompareTo
method. - Is
HashSet
the best way to have a list of unique objects?
It uses an IEqualityComparer<T>
(EqualityComparer<T>.Default
unless you specify a different one on construction).
When you add an element to the set, it will find the hash code using IEqualityComparer<T>.GetHashCode
, and store both the hash code and the element (after checking whether the element is already in the set, of course).
To look an element up, it will first use the IEqualityComparer<T>.GetHashCode
to find the hash code, then for all elements with the same hash code, it will use IEqualityComparer<T>.Equals
to compare for actual equality.
That means you have two options:
- Pass a custom
IEqualityComparer<T>
into the constructor. This is the best option if you can't modify theT
itself, or if you want a non-default equality relation (e.g. "all users with a negative user ID are considered equal"). This is almost never implemented on the type itself (i.e.Foo
doesn't implementIEqualityComparer<Foo>
) but in a separate type which is only used for comparisons. - Implement equality in the type itself, by overriding
GetHashCode
andEquals(object)
. Ideally, implementIEquatable<T>
in the type as well, particularly if it's a value type. These methods will be called by the default equality comparer.
Note how none of this is in terms of an ordered comparison - which makes sense, as there are certainly situations where you can easily specify equality but not a total ordering. This is all the same as Dictionary<TKey, TValue>
, basically.
If you want a set which uses ordering instead of just equality comparisons, you should use SortedSet<T>
from .NET 4 - which allows you to specify an IComparer<T>
instead of an IEqualityComparer<T>
. This will use IComparer<T>.Compare
- which will delegate to IComparable<T>.CompareTo
or IComparable.CompareTo
if you're using Comparer<T>.Default
.
Here's clarification on a part of the answer that's been left unsaid: The object type of your HashSet<T>
doesn't have to implement IEqualityComparer<T>
but instead just has to override Object.GetHashCode()
and Object.Equals(Object obj)
.
Instead of this:
public class a : IEqualityComparer<a>
{
public int GetHashCode(a obj) { /* Implementation */ }
public bool Equals(a obj1, a obj2) { /* Implementation */ }
}
You do this:
public class a
{
public override int GetHashCode() { /* Implementation */ }
public override bool Equals(object obj) { /* Implementation */ }
}
It is subtle, but this tripped me up for the better part of a day trying to get HashSet to function the way it is intended. And like others have said, HashSet<a>
will end up calling a.GetHashCode()
and a.Equals(obj)
as necessary when working with the set.
HashSet
uses Equals
and GetHashCode()
.
CompareTo
is for ordered sets.
If you want unique objects, but you don't care about their iteration order, HashSet<T>
is typically the best choice.
constructor HashSet receive object what implement IEqualityComparer for adding new object. if you whant use method in HashSet you nead overrride 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");
}
}
}
来源:https://stackoverflow.com/questions/8952003/how-does-hashset-compare-elements-for-equality