Як користуватися 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
Джеремі Рей Браун

Відповіді:


173

Ваша 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 Написання коду, не знаючи, що він насправді робить, називається програмою культового вантажу . Це напрочуд широко поширена практика. Це принципово не працює.


19
Ваша рівність не вдається, коли або x, або y є нульовими.
dzendras

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

49
Ого. Гидота надто жорстка. Ми тут, щоб допомагати одне одному, а не ображати. Думаю, це викликає сміх у деяких людей, але я б порадив його видалити.
Джес

4
+1 за те, що я змусив мене прочитати про "програмування культового вантажу" на wiki, а потім змінити рядок тегів Skype на "// Тут починається глибока магія ... за якою йде важка майстерність".
Олексій

4
@NeilBenn Ви помилково приймаєте відверту пораду за хамство. Оскільки 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();Див. цю публікацію щодо його використання.
орад

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

Я ніколи не стикався з цим, але пам’ятатиму це зробити наступного разу. У вас є нуль у списку <T> або щось подібне?
Simon_Weaver

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

На жаль Виправлена ​​помилка :)
Simon_Weaver
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.