Intersect with a custom IEqualityComparer using Linq

Long story short: I have 2 collections of objects. One contains good values (Let's call it "Good"), the other default values (Mr. "Default"). I want the Intersect of the Union between Good and Default, and Default. In other words: Intersect(Union(Good, Default), Default). One might think it resolves as Default, but here is where it gets tricky : I use a custom IEqualityComparer.

I got the following classes :

class MyClass
{
    public string MyString1;
    public string MyString2;
    public string MyString3;
}

class MyEqualityComparer : IEqualityComparer<MyClass>
{
    public bool Equals(MyClass item1, MyClass item2)
    {
        if(item1 == null && item2 == null)
            return true;
        else if((item1 != null && item2 == null) ||
                (item1 == null && item2 != null))
            return false;

        return item1.MyString1.Equals(item2.MyString1) &&
               item1.MyString2.Equals(item2.MyString2);
    }

    public int GetHashCode(MyClass item)
    {
        return new { item.MyString1, item.MyString2 }.GetHashCode();
    }
}

Here are the characteristic of my collections Good and Default collections :

Default : It's a large set, containing all the wanted { MyString1, MyString2 } pairs, but the MyString3 values are, as you can guess, default values.

Good : It's a smaller set, containing mostly items which are in the Default set, but with some good MyString3 values. It also has some { MyString1, MyString2 } that are outside of the wanted set.

What I want to do is this : Take only the items from Good that are in Default, but add the other items in Default to that.

Here is, what I think is, my best try :

HalfWantedResult = Good.Union(Default, new MyEqualityComparer());
WantedResult= HalfWantedResult.Intersect(Good, new MyEqualityComparer());

I taught it should have worked, but the result I get is basically only the good { MyString1, MyString2 } pairs set, but all coming from the Default set, so I have the default value all across. I also tried switching the Default and Good of the last Intersect, but I get the same result.


Solution 1:

First of all this is wrong:

public bool Equals(MyClass item1, MyClass item2)
{
    return GetHashCode(item1) == GetHashCode(item2);
}

If the hashcode's are different for sure the corresponding 2 items are different, but if they're equal is not guaranteed that the corresponding 2 items are equal.

So this is the correct Equals implementation:

public bool Equals(MyClass item1, MyClass item2)
{
    if(object.ReferenceEquals(item1, item2))
        return true;
    if(item1 == null || item2 == null)
        return false;
    return item1.MyString1.Equals(item2.MyString1) &&
           item1.MyString2.Equals(item2.MyString2);
}

As Slacks suggested (anticipating me) the code is the following:

var Default = new List<MyClass>
{
    new MyClass{MyString1="A",MyString2="A",MyString3="-"},
    new MyClass{MyString1="B",MyString2="B",MyString3="-"},
    new MyClass{MyString1="X",MyString2="X",MyString3="-"},
    new MyClass{MyString1="Y",MyString2="Y",MyString3="-"},
    new MyClass{MyString1="Z",MyString2="Z",MyString3="-"},

};
var Good = new List<MyClass>
{
    new MyClass{MyString1="A",MyString2="A",MyString3="+"},
    new MyClass{MyString1="B",MyString2="B",MyString3="+"},
    new MyClass{MyString1="C",MyString2="C",MyString3="+"},
    new MyClass{MyString1="D",MyString2="D",MyString3="+"},
    new MyClass{MyString1="E",MyString2="E",MyString3="+"},
};
var wantedResult = Good.Intersect(Default, new MyEqualityComparer())
                       .Union(Default, new MyEqualityComparer());

// wantedResult:
// A A +
// B B +
// X X -
// Y Y -
// Z Z -

Solution 2:

You need to check for actual equality, not just hashcode equality.

GetHashCode() is not (and cannot be) collision free, which is why the Equals method is required in the first place.

Also, you can do this much more simply by writing

WantedResult = Good.Concat(Default).Distinct();

The Distinct method will return the first item of each pair of duplicates, so this will return the desired result.

EDIT: That should be

WantedResult = Good.Intersect(Default, new MyEqualityComparer())
                   .Union(Default, new MyEqualityComparer());