Matjaž
Matjaž

Reputation: 2115

Get distinct values from HashSet

I want to get only distinct values from HashSet, I have implemented IEquatable, IEqualityComparer, but still can't get distinct values.

class Program
{
    static void Main(string[] args)
    {

        HashSet<Item> items = new HashSet<Item>()
        {
            {new Item("item1")},
            {new Item("item2")},
            {new Item("item3")},
            {new Item("item1")}
        };

        foreach (var item in items.Distinct())
        {
            Console.WriteLine(item.Name);
        }

        Console.ReadKey();

    }
}


class Item : IEquatable<Item>, IEqualityComparer<Item>
{
    public string Name { get; set; }
    public Item(string name)
    {
        this.Name = name;
    }

    public bool Equals(Item other)
    {
        return this.Name.Equals(other.Name);
    }

    public bool Equals(Item x, Item y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(Item obj)
    {
        return this.Name.GetHashCode();
    }
}

Console output:

item1
item2
item3
item1

Thanks!

Upvotes: 0

Views: 1894

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149518

If you only have a single implementation which defines equality of your class, implementing IEquatable<T> (properly) is enough. You shouldn't implement IEqualityComparer<T> as well. The latter is meant to be, usually as in a separate class, when you want to provide multiple ways to define uniqueness between two elements of a type.

Other than that, Your method signature for GetHashCode is wrong. Currently, it defers to object.Equals instead of your custom implementation.

You need to add the override keyword to your GetHashCode implementation and remove the Item obj from the signature:

public override int GetHashCode()
{
    return this.Name.GetHashCode();
}

And also override object.Equals as well to use your Equals(item other):

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((Item) obj);
}

public override bool Equals( You don't need to call Distinct() on a HashSet<T> as it by itself guarantees uniqueness in its internal collection, given that you provide a proper IEquatable<T> override or supply it with an IEqualityComparer<T>.

From the docs of HashSet<T>.Add:

Return Value:

Type: System.Boolean true if the element is added to the HashSet object; false if the element is already present.

Upvotes: 3

Mat&#237;as Fidemraizer
Mat&#237;as Fidemraizer

Reputation: 64933

First of all, you should implement IEqualityComparer<T> as a separate class and you need to provide the whole equality comparer during HashSet<T> construction:

var set = new HashSet<CustomClass>(new CustomClassEqualityComparer());

If you go with the equality comparer way, you aren't forced to implement IEquatable<T>:

public class ItemEqualityComparer : IEqualityComparer<Item>
{
    public bool Equals(Item x, Item y)
    {
        return x.Name == y.Name;
    }

    public int GetHashCode(Item obj)
    {
        return obj.Name.GetHashCode();
    }
}

Furthermore, you can design many IEqualityComparer<T> implementations to cover many use cases that can define different uniqueness meanings to the same object (i.e. Item).

If you provide a good implementation of IEqualityComparer<T>, you won't need Distinct since a HashSet<T> is a set and this means that it's an unordered collection of unique elements, and the whole set will use your equality comparer to check if a given element is present in the set (thus, all elements are unique in the same set!).

Upvotes: 2

Related Questions