Пересечение с пользовательским IEqualityComparer с использованием Linq

Короче говоря: у меня есть 2 коллекции объектов. Один содержит хорошие значения (назовем его "Хороший"), другой значения по умолчанию (Мистер "По умолчанию"). Я хочу Пересечение Союза между Добром и По умолчанию и По умолчанию. Другими словами: Intersect(Union(Good, Default), Default). Можно подумать, что он разрешается как 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 collections:

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

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

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

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

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

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


person Tipx    schedule 02.12.2010    source источник
comment
Ваша реализация Equals действительно плоха. Будут хеш-коллизии, которых быть не должно. Почему бы не использовать ту же проекцию (new { item.MyString1, item.MyString2 }), но вызвать Equals?   -  person Jon Skeet    schedule 03.12.2010
comment
Я должен изучить это, это может быть частью проблемы. Union использует GetHashCode, а Intersects использует Equals. Я действительно не вкладывал в эту часть никаких уроков. стыдно   -  person Tipx    schedule 03.12.2010


Ответы (2)


Во-первых, это неправильно:

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

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

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 -
person digEmAll    schedule 02.12.2010
comment
+1 за то, что очень помогли мне с моими равными, но не приняты, так как это не решает проблему. (Хотел бы я +2! :-P ) - person Tipx; 03.12.2010
comment
Нет проблем. Я писал правильное решение вашей проблемы с перекрестком, но Слакс опередил меня. Его ответ ;-) - person digEmAll; 03.12.2010
comment
Я чувствую себя таким глупым... Я неправильно понял всплывающую подсказку о методе пересечения, и в моих базовых данных была ошибка, поэтому совпадение не могло произойти. Поскольку я считаю этот пост самым полезным, с реализацией equals и примерами, я его принял. Спасибо еще раз. - person Tipx; 03.12.2010
comment
Пожалуйста ;-). Между прочим, если ваш Comparer вызывается очень много раз, а производительность действительно важна для вас, вам следует подумать о том, чтобы переписать также ваш GetHashCode(), чтобы избежать создания нового экземпляра анонимного типа для каждого вызова. Посмотрите на этот ответ, чтобы получить быструю и эффективную реализацию --› stackoverflow.com/questions/263400/ - person digEmAll; 03.12.2010
comment
Я сделаю это. Не то чтобы производительность действительно была проблемой для моего проекта (только проверка около миллиона раз в день), но я стремлюсь к лучшему программированию! (Я не настоящий программист :-P) - person Tipx; 03.12.2010
comment
На самом деле, использование реализации анонимного типа для вычисления HasCode для нескольких полей — действительно умная идея, которая делает ваш код коротким и чистым. Но, очевидно, неплохо знать, что происходило за кулисами, и код в ответе, который я разместил выше, тот же самый, который используется внутри метода GetHashCode анонимных типов ;-) - person digEmAll; 03.12.2010
comment
Эта реализация Equals неоптимальна. Преамбула очень тяжело читается. Вместо этого рассмотрите if (object.ReferenceEquals(item1,item2)) return true; для первой строки. Это обрабатывает случай, когда оба являются нулевыми, ускоряет случай, когда оба являются одной и той же ссылкой. Тогда остальная часть нулевой проверки — это просто if(item1==null||item2==null) return false;, который короче и легче читается, и является правильным, поскольку, если они оба равны нулю, вы уже вернули истину. - person Kevin Cathcart; 27.02.2012
comment
@KevinCathcart: Да, спасибо, ты прав. Отредактировано. Наверное, я написал это, не особо задумываясь о выступлениях... - person digEmAll; 27.02.2012

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

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

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

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

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

EDIT: это должно быть

WantedResult = Good.Intersect(Default, new MyEqualityComparer())
                   .Union(Default, new MyEqualityComparer());
person SLaks    schedule 02.12.2010
comment
Если я использую Concat & Distinct, я получу желаемый результат, но у меня также будут некоторые элементы из набора Good, которых не было в желаемой области действия (по умолчанию). - person Tipx; 03.12.2010
comment
Операции Union и Intersect являются коммутативными, поэтому они не меняют результат, но да, они избегают использования переменной посредника. - person Tipx; 03.12.2010
comment
О, подождите, Union и Intersect не коммутативны: (ABC u ABD) n AE = A в то время как (ABC n AE) u ABD = ABD - person digEmAll; 03.12.2010
comment
О, извините, я думал только о своем случае: (ABC u ABD) n ABC = ABC и ABC u (ABD n ABC) = ABC. Но да, вы правы, их нет. И в моем случае (ABC u ABD) != (ABD u ABC), так как это не настоящий союз. - person Tipx; 03.12.2010