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

У меня есть несколько колокольчиков в моей базе данных с тем же номером. Я хочу получить их все без дублирования. Затем я создаю класс сравнения для выполнения этой работы, но выполнение функции делает большую задержку от функции без distinct, от 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;
        }

    }

6 ответов


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

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

в коде, решение направить GetHashCode to Class_reglement.Numf.GetHashCode и реализовать его вот так.

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

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

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


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


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

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

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

вы должны попробуйте вместо этого разработать предложение where, удовлетворяющее вашим требованиям, см. использование IEqualityComparer с LINQ для сущностей, кроме предложения для получения более подробной информации.


просто код, с выполнения 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 distinct by Numf

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

Если вы хотите общее решение без бокса:

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)

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 работают с entity framework непосредственно на стороне базы данных (я думаю, вы это знаете, потому что AsEnumerable), но это гораздо более простой способ сделать простые равенства для общего случая.

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