Jak używać IEqualityComparer

97

W mojej bazie danych mam kilka dzwonków o tym samym numerze. Chcę je wszystkie zdobyć bez powielania. Stworzyłem klasę porównawczą, aby wykonać tę pracę, ale wykonanie funkcji powoduje duże opóźnienie funkcji bez wyraźnego, od 0,6 s do 3,2 s!

Czy robię to dobrze, czy muszę użyć innej metody?

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;
    }
}
Akrem
źródło
16
Możesz
rzucić
Ten blog wyjaśnia, jak doskonale korzystać z IEqualityComparer: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Odpowiedzi:

174

Twoja GetHashCodeimplementacja zawsze zwraca tę samą wartość. Distinctopiera się na dobrej funkcji skrótu, aby działać wydajnie, ponieważ wewnętrznie tworzy tabelę skrótów .

Przy implementacji interfejsów klas ważne jest, aby zapoznać się z dokumentacją , aby wiedzieć, który kontrakt masz zaimplementować. 1

W Twoim kodzie rozwiązaniem jest przekazanie GetHashCodego Class_reglement.Numf.GetHashCodei odpowiednie wdrożenie.

Poza tym Twoja Equalsmetoda jest pełna niepotrzebnego kodu. Można go przepisać w następujący sposób (ta sama semantyka, ¼ kodu, bardziej czytelny):

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

Wreszcie ToListpołączenie jest niepotrzebne i czasochłonne: AddRangeakceptuje każdą, IEnumerablewięc konwersja do a Listnie jest wymagana. AsEnumerablejest tutaj również zbędne, ponieważ przetwarzanie wyniku w i AddRangetak spowoduje to.


1 Pisanie kodu bez wiedzy, co właściwie robi, nazywa się programowaniem kultowym cargo . To zaskakująco powszechna praktyka. To zasadniczo nie działa.

Konrad Rudolph
źródło
20
Twoje Równe zawodzi, gdy x lub y jest zerowe.
dzendras
4
@dzendras To samo dla GetHashCode. Zwróć jednak uwagę, że dokumentacjaIEqualityComparer<T> nie określa, co zrobić z nullargumentami - ale przykłady podane w artykule również nie obsługują null.
Konrad Rudolph
50
Łał. Obrzydliwość jest niepotrzebnie surowa. Jesteśmy po to, aby sobie nawzajem pomagać, a nie obrażać. Wydaje mi się, że niektórzy ludzie się śmieją, ale polecam to usunięcie.
Jess
5
+1 za zmuszenie mnie do przeczytania o "programowaniu kultowym cargo" na wiki, a następnie zmianę mojego sloganu na skype na "// Tutaj zaczyna się głęboka magia ... po której następuje ciężka magia".
Alex
4
@NeilBenn Szczerą radę mylisz z niegrzecznością. Ponieważ OP zaakceptował odpowiedź (i, mogę zauważyć, w znacznie surowszej wersji!), Wydawało się, że nie popełnili tego samego błędu. Nie jestem pewien, dlaczego uważasz, że udzielanie porad jest niegrzeczne, ale mylisz się, mówiąc, że „facet nie potrzebuje wykładu”. Zdecydowanie się nie zgadzam: wykład był potrzebny i wzięto go sobie do serca. Kod, tak jak napisano, był zły i oparty na złych praktykach. Nie zwracanie na to uwagi byłoby złą przysługą i wcale nie pomocne, ponieważ wtedy PO nie mógł ulepszyć ich działania.
Konrad Rudolph
47

Wypróbuj ten kod:

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

Przykład jego użycia to

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 
suneelsarraf
źródło
19
GetHashCoderównież musi użyć wyrażenia: return _expr.Invoke(obj).GetHashCode();Zobacz ten post, aby zobaczyć, jak to zrobić.
orad
3

Tylko kod, z implementacją GetHashCodei NULLwalidacją:

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

Przykład: lista Class_reglement różni się od Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());
Shahar Shokrani
źródło
2

Włączenie Twojej klasy porównawczej (a dokładniej klasy AsEnumerable wywołania, którego potrzebujesz, aby to zadziałało) oznaczało, że logika sortowania przeszła z bazowania na serwerze bazy danych do bycia na kliencie bazy danych (twojej aplikacji). Oznaczało to, że klient musi teraz pobrać, a następnie przetworzyć większą liczbę rekordów, co zawsze będzie mniej wydajne niż wyszukiwanie w bazie danych, w której można użyć odpowiednich indeksów.

Zamiast tego należy spróbować opracować klauzulę Where, która spełnia Twoje wymagania, zobacz Używanie IEqualityComparer z klauzulą ​​LINQ to Entities Except, aby uzyskać więcej informacji.

Justin
źródło
2

Jeśli chcesz mieć ogólne rozwiązanie bez boksu:

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

stosowanie:

KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf)
user764754
źródło
0

IEquatable<T> może być o wiele łatwiejszym sposobem na zrobienie tego z nowoczesnymi frameworkami.

Otrzymujesz ładną, prostą bool Equals(T other)funkcję i nie ma problemów z rzutowaniem lub tworzeniem oddzielnej klasy.

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

Zauważ, że musisz zaimplementować, GetHashCodejeśli używasz tego w słowniku lub z czymś podobnym Distinct.

PS. Nie sądzę, aby jakiekolwiek niestandardowe metody Equals działały ze strukturą encji bezpośrednio po stronie bazy danych (myślę, że wiesz o tym, ponieważ robisz AsEnumerable), ale jest to znacznie prostsza metoda wykonania prostego Equals dla ogólnego przypadku.

Jeśli wydaje się, że coś nie działa (na przykład powielone błędy klucza podczas wykonywania ToDictionary), umieść punkt przerwania w Equals, aby upewnić się, że został trafiony, i upewnij się, że GetHashCodezdefiniowałeś (za pomocą słowa kluczowego override).

Simon_Weaver
źródło
1
Nadal musisz sprawdzić, czy nie ma null
disklosr
Nigdy na to nie wpadłem, ale przypomnę to następnym razem. Czy masz wartość null w List <T> czy coś w tym stylu?
Simon_Weaver
1
Zgodnie z .Equals()metodą, którą wydajesz się porównywać other.Hometowndo siebie samego, zamiastthis.Hometown
Jake Stokes
Ups. Naprawiono literówkę :)
Simon_Weaver