C# Distinct на IEnumerable‹T› с пользовательским IEqualityComparer

Вот что я пытаюсь сделать. Я запрашиваю файл XML, используя LINQ to XML, который дает мне объект IEnumerable<T>, где T — мой класс «Деревня», заполненный результатами этого запроса. Некоторые результаты дублируются, поэтому я хотел бы выполнить Distinct() для объекта IEnumerable, например:

public IEnumerable<Village> GetAllAlliances()
{
    try
    {
        IEnumerable<Village> alliances =
             from alliance in xmlDoc.Elements("Village")
             where alliance.Element("AllianceName").Value != String.Empty
             orderby alliance.Element("AllianceName").Value
             select new Village
             {
                 AllianceName = alliance.Element("AllianceName").Value
             };

        // TODO: make it work...
        return alliances.Distinct(new AllianceComparer());
    }
    catch (Exception ex)
    {
        throw new Exception("GetAllAlliances", ex);
    }
}

Поскольку компаратор по умолчанию не будет работать для объекта Village, я реализовал собственный, как показано здесь, в классе AllianceComparer:

public class AllianceComparer : IEqualityComparer<Village>
{
    #region IEqualityComparer<Village> Members
    bool IEqualityComparer<Village>.Equals(Village x, Village y)
    {
        // Check whether the compared objects reference the same data.
        if (Object.ReferenceEquals(x, y)) 
            return true;

        // Check whether any of the compared objects is null.
        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            return false;

        return x.AllianceName == y.AllianceName;
    }

    int IEqualityComparer<Village>.GetHashCode(Village obj)
    {
        return obj.GetHashCode();
    }
    #endregion
}

Метод Distinct() не работает, так как у меня одинаковое количество результатов с ним или без него. Другое дело, и я не знаю, возможно ли это обычно, но я не могу войти в AllianceComparer.Equals(), чтобы увидеть, в чем может быть проблема.
Я нашел примеры этого в Интернете, но я могу похоже, моя реализация не работает.

Надеюсь, кто-то здесь может увидеть, что здесь может быть не так! Заранее спасибо!


person Fueled    schedule 11.01.2009    source источник
comment
Ваша конструкция catch/throw приводит к тому, что вызывающая функция больше не может выбрать catch(ArgumentException) или catch(IOException) (примеры). В этом случае вам, вероятно, лучше удалить try/catch целиком — к тому же имя метода будет частью свойства StackTrace исключения.   -  person Sam Harwell    schedule 16.12.2009


Ответы (3)


Проблема с вашим GetHashCode. Вы должны изменить его, чтобы вместо этого возвращался хэш-код AllianceName.

int IEqualityComparer<Village>.GetHashCode(Village obj)
{
    return obj.AllianceName.GetHashCode();
}

Дело в том, что если Equals возвращает true, объекты должны иметь одинаковый хэш-код, чего нельзя сказать о разных объектах Village с одним и тем же AllianceName. Поскольку Distinct работает путем внутреннего построения хэш-таблицы, вы получите одинаковые объекты, которые вообще не будут сопоставляться из-за разных хеш-кодов.

Точно так же, чтобы сравнить два файла, если хеш двух файлов не совпадает, вам вообще не нужно проверять сами файлы. Они будут другими. В противном случае вы будете продолжать проверять, действительно ли они одинаковы или нет. Именно так ведет себя хеш-таблица, которую использует Distinct.

person mmx    schedule 11.01.2009
comment
Почему мы не можем просто переопределить типичные методы равенства для Distinct?? - person Boog; 24.03.2013
comment
@Boog Конечно, вы можете сделать это в самом объекте или в отдельном классе компаратора равенства, как того хочет ОП. Случай использования пользовательского компаратора равенства — это когда у вас есть разные способы считать объекты равными или нет (например, сравнение с учетом регистра и без учета регистра для строк) или когда вы не можете изменить сам класс по какой-либо причине. В любом случае вы должны переопределить как Equals, так и GetHashCode и написать правильный GetHashCode по отношению к методу Equals. - person mmx; 24.03.2013

return alliances.Select(v => v.AllianceName).Distinct();

Это вернет IEnumerable<string> вместо IEnumerable<Village>.

person superrcat    schedule 16.12.2009

Или изменить строку

return alliances.Distinct(new AllianceComparer());

to

return alliances.Select(v => v.AllianceName).Distinct();
person Jacob Seleznev    schedule 24.11.2009
comment
Это интересный альтернативный подход, который заставит автора вопроса переосмыслить код, использующий IEqualityComparer. Это решает проблему для меня, поэтому мне это нравится. Однако это не отвечает на вопрос. В моем случае я хотел извлечь уникальные ключи из набора данных "ключ-значение" и сохранить уникальные ключи отдельно в списке. Как уже упоминалось @superrcat, при этом изменяется общий тип возвращаемого IEnumerable. - person Greg; 29.04.2015