Как использовать IEqualityComparer


97

У меня в базе есть несколько колокольчиков с таким же номером. Я хочу получить их все без дублирования. Я создал класс сравнения для выполнения этой работы, но выполнение функции вызывает большую задержку от функции без различия, от 0,6 до 3,2 секунды!

Правильно ли я делаю или мне нужно использовать другой метод?

reg.AddRange(
    (from a in this.dataContext.reglements
     join b in this.dataContext.Clients on a.Id_client equals b.Id
     where a.date_v <= datefin && a.date_v >= datedeb
     where a.Id_client == b.Id
     orderby a.date_v descending 
     select new Class_reglement
     {
         nom  = b.Nom,
         code = b.code,
         Numf = a.Numf,
     })
    .AsEnumerable()
    .Distinct(new Compare())
    .ToList());

class Compare : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x.Numf == y.Numf)
        {
            return true;
        }
        else { return false; }
    }
    public int GetHashCode(Class_reglement codeh)
    {
        return 0;
    }
}

16
Возможно, вы захотите взглянуть на Рекомендации и правила для GetHashCode
Конрад Фрикс,

В этом блоге объясняется, как идеально использовать IEqualityComparer: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Джереми Рэй Браун,

Ответы:


174

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

При реализации интерфейсов классов важно читать документацию , чтобы знать, какой контракт вы должны реализовать. 1

В вашем коде решение состоит в том, чтобы переслать GetHashCodeего Class_reglement.Numf.GetHashCodeи соответствующим образом реализовать там.

Кроме того, ваш Equalsметод полон ненужного кода. Его можно было бы переписать следующим образом (семантика та же, ¼ кода, более читабельно):

public bool Equals(Class_reglement x, Class_reglement y)
{
    return x.Numf == y.Numf;
}

Наконец, ToListвызов является ненужным и занимает много времени: AddRangeпринимает любой, IEnumerableпоэтому преобразование в a Listне требуется. AsEnumerableздесь также является избыточным, поскольку обработка результата в AddRangeлюбом случае вызовет это.


1 Написание кода, не зная, что он на самом деле делает, называется программированием культа карго . Это удивительно распространенная практика. Принципиально не работает.


20
Ваш Equals терпит неудачу, если x или y равны нулю.
дзендрас

4
@dzendras То же самое для GetHashCode. Однако обратите внимание, что в документацииIEqualityComparer<T> не указано, что делать с nullаргументами, но примеры, приведенные в статье, также не обрабатываются null.
Конрад Рудольф

50
Вот это да. Мерзость неоправданно сурова. Мы здесь, чтобы помогать друг другу, а не оскорблять. Я думаю, это заставляет некоторых людей смеяться, но я бы рекомендовал удалить его.
Джесс

5
+1 за то, что заставил меня прочитать в вики о "карго-культовом программировании", а затем изменить строку моего тега в скайпе на "// Глубокая магия начинается здесь ... с последующим тяжелым волшебством".
Alex

4
@NeilBenn Вы принимаете откровенный совет за грубость. Поскольку OP принял ответ (и, я могу заметить, в гораздо более строгой версии!), Похоже, они не совершили ту же ошибку. Не знаю, почему вы считаете, что давать советы грубо, но вы ошибаетесь, когда говорите, что «этому парню не нужна лекция». Я категорически не согласен: лекция была нужна, и ее приняли близко к сердцу. Код в том виде, в котором он был написан, был плохим и основан на плохой практике работы. Не указывать на это было бы медвежьей услугой и совсем не полезно, поскольку тогда OP не мог улучшить их работу.
Конрад Рудольф

47

Попробуйте этот код:

public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericCompare(Func<T, object> expr)
    {
        this._expr = expr;
    }
    public bool Equals(T x, T y)
    {
        var first = _expr.Invoke(x);
        var sec = _expr.Invoke(y);
        if (first != null && first.Equals(sec))
            return true;
        else
            return false;
    }
    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }
}

Примером его использования может быть

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 

19
GetHashCodeтакже необходимо использовать это выражение: return _expr.Invoke(obj).GetHashCode();см. этот пост, чтобы узнать его использование.
orad

3

Просто код с реализацией GetHashCodeи NULLпроверкой:

public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x is null || y is null))
            return false;

        return x.Numf == y.Numf;
    }

    public int GetHashCode(Class_reglement product)
    {
        //Check whether the object is null 
        if (product is null) return 0;

        //Get hash code for the Numf field if it is not null. 
        int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();

        return hashNumf;
    }
}

Пример: список Class_reglement, отличный от Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());

2

Включение вашего класса сравнения (или, точнее, AsEnumerableвызова, который вам нужно было использовать, чтобы заставить его работать), означало, что логика сортировки перешла от основанной на сервере базы данных к клиенту базы данных (вашему приложению). Это означало, что теперь вашему клиенту необходимо получить, а затем обработать большее количество записей, что всегда будет менее эффективным, чем выполнение поиска в базе данных, где можно использовать соответствующие индексы.

Вместо этого вам следует попытаться разработать предложение where, которое удовлетворяет вашим требованиям. Дополнительные сведения см. В разделе Использование IEqualityComparer с предложением LINQ to Entities Except .


2

Если вам нужно универсальное решение без бокса:

public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    private readonly Func<T, TKey> _keyGetter;

    public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
    {
        _keyGetter = keyGetter;
    }

    public bool Equals(T x, T y)
    {
        return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
    }

    public int GetHashCode(T obj)
    {
        TKey key = _keyGetter(obj);

        return key == null ? 0 : key.GetHashCode();
    }
}

public static class KeyBasedEqualityComparer<T>
{
    public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
    {
        return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
    }
}

использование:

KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf)

0

IEquatable<T> может быть гораздо более простым способом сделать это с помощью современных фреймворков.

Вы получаете красивую простую bool Equals(T other)функцию, и вам не придется возиться с приведением типов или созданием отдельного класса.

public class Person : IEquatable<Person>
{
    public Person(string name, string hometown)
    {
        this.Name = name;
        this.Hometown = hometown;
    }

    public string Name { get; set; }
    public string Hometown { get; set; }

    // can't get much simpler than this!
    public bool Equals(Person other)
    {
        return this.Name == other.Name && this.Hometown == other.Hometown;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();  // see other links for hashcode guidance 
    }
}

Обратите внимание, что вам НЕОБХОДИМО реализовать, GetHashCodeесли вы используете это в словаре или с чем-то вроде Distinct.

PS. Я не думаю, что какие-либо пользовательские методы Equals работают со структурой сущностей непосредственно на стороне базы данных (я думаю, вы знаете это, потому что используете AsEnumerable), но это гораздо более простой метод для выполнения простых Equals для общего случая.

Если что-то не работает (например, повторяющиеся ключевые ошибки при выполнении ToDictionary), поместите точку останова внутри Equals, чтобы убедиться, что она попала, и убедитесь, что вы GetHashCodeопределили (с ключевым словом override).


1
Вам все еще нужно проверить на нуль
disklosr

Я никогда не сталкивался с этим, но я не забуду сделать это в следующий раз. У вас есть null в List <T> или что-то в этом роде?
Simon_Weaver

1
Под .Equals()методом вы , как представляется, по сравнению other.Hometownс собой, аthis.Hometown
Джейк Стокса

Ой. Исправлена ​​опечатка :)
Simon_Weaver
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.