Пересечение с пользовательским IEqualityComparer с помощью Linq - PullRequest
14 голосов
/ 03 декабря 2010

Короче говоря: у меня есть 2 коллекции объектов. Один содержит хорошие значения (назовем его «Хороший»), другой - значения по умолчанию (г-н «По умолчанию»). Я хочу пересечение союза между добром и дефолтом и дефолтом. Другими словами: пересечение (объединение (хорошее, по умолчанию), по умолчанию). Кто-то может подумать, что он разрешен как Default, но вот где это становится сложнее: я использую собственный IEqualityComparer.

Я получил следующие классы:

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();
    }
}

Вот характеристика моих коллекций Good и Default collection:

По умолчанию: это большой набор, содержащий все требуемые пары {MyString1, MyString2}, но значения MyString3, как вы можете догадаться, являются значениями по умолчанию.

Хорошо: это меньший набор, содержащий в основном элементы из набора по умолчанию, но с некоторыми хорошими значениями MyString3. У него также есть некоторые {MyString1, MyString2}, которые находятся за пределами желаемого набора.

Что я хочу сделать, так это: взять только те предметы из «Хорошего», которые находятся в «По умолчанию», но добавить к этому другие предметы в «По умолчанию».

Вот, что я думаю, моя лучшая попытка:

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

Я учил, что это должно было сработать, но в результате я получаю только набор хороших пар {MyString1, MyString2}, но все они получены из набора по умолчанию, поэтому у меня есть значение по умолчанию. Я также попытался переключить Default и Good последнего Intersect, но я получил тот же результат.

Ответы [ 2 ]

19 голосов
/ 03 декабря 2010

Прежде всего, это неправильно:

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

Если хеш-коды различны, то соответствующие 2 элемента различны, но если они равны, не гарантируется, что соответствующие 2 элемента равны.

Так что это правильная реализация Equals:

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);
}

Как и предполагал Слакс (ожидая меня) код следующий:

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 -
10 голосов
/ 03 декабря 2010

Вам нужно проверять фактическое равенство, а не только равенство хеш-кода.

GetHashCode() не является (и не может быть) свободным от столкновений, поэтому метод Equals необходим в первую очередь.

Кроме того, вы можете сделать это намного проще, написав

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

Метод Distinct вернет первый элемент каждой пары дубликатов, поэтому он вернет желаемый результат.

РЕДАКТИРОВАТЬ : Это должно быть

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