Search code examples
c#linqiequalitycomparerset-operations

IEqualityComparer<T> custom implementation and set-operations


i need to perform simple set-operations in linq (for example Union, Except and Intersect)

class Person {
        public int Id { get; set; }
        public string Name { get; set; }

        public Person() { }

        public Person(int id, string name) {
            Id = id; Name = name;
        }

    }

Comparer implementation:

class PersonComparer : IEqualityComparer<Person> {
        public bool Equals(Person x, Person y) {
            return x.Id == y.Id;
        }

        public int GetHashCode(Person p) {
            return p.GetHashCode();
        }
    }

Populating lists:

var list1 = new List<Person>();
        list1.Add(new Person(1, "John"));
        list1.Add(new Person(2, "Peter"));
        list1.Add(new Person(3, "Mike"));

        var list2 = new List<Person>();
        list2.Add(new Person(2, "Peter"));
        list2.Add(new Person(3, "Mike"));
        list2.Add(new Person(4, "Fred"));

    var comparer = new PersonComparer();

    var list3 = list1.Intersect(list2, comparer).ToList(); // **Empty List**
    var list4 = list1.Except(list2, comparer).ToList(); // **"John", "Peter", "Mike"**

It seems that my comparer does not work. Why?


Solution

  • The problem is your implementation of GetHashCode(Person p). As noted in MSDN:

    Implementations are required to ensure that if the Equals method returns true for two objects x and y, then the value returned by the GetHashCode method for x must equal the value returned for y.

    In your case, p.GetHashCode() may return a different value for each p in memory, even if they have the same Id—that is, two different instances of Person may have the same Id but different hash codes—so this isn't sufficient to satisfy the requirement noted above for a proper implementation of GetHashCode(Person p). Instead, use something like this:

    public int GetHashCode(Person p) {
        return p.Id;
    }