Come utilizzare IEqualityComparer


97

Ho alcune campane nel mio database con lo stesso numero. Voglio ottenerli tutti senza duplicarli. Ho creato una classe di confronto per fare questo lavoro, ma l'esecuzione della funzione provoca un grande ritardo dalla funzione senza distinti, da 0.6 sec a 3.2 sec!

Lo sto facendo bene o devo usare un altro metodo?

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
Potresti dare un'occhiata a Linee guida e regole per GetHashCode
Conrad Frix

Questo blog spiega come utilizzare perfettamente IEqualityComparer: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Risposte:


173

La tua GetHashCodeimplementazione restituisce sempre lo stesso valore. Distinctsi basa su una buona funzione hash per lavorare in modo efficiente perché crea internamente una tabella hash .

Quando si implementano interfacce di classi è importante leggere la documentazione , per sapere quale contratto si suppone di implementare. 1

Nel codice, la soluzione è quella di inoltrare GetHashCodeper Class_reglement.Numf.GetHashCodee implementare in modo appropriato lì.

A parte questo, il tuo Equalsmetodo è pieno di codice non necessario. Potrebbe essere riscritto come segue (stessa semantica, ¼ del codice, più leggibile):

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

Infine, la ToListchiamata non è necessaria e richiede tempo: AddRangeaccetta qualsiasi, IEnumerablequindi la conversione in a Listnon è richiesta. AsEnumerableè ridondante anche qui poiché l'elaborazione del risultato in AddRangelo causerà comunque.


1 Scrivere codice senza sapere cosa fa effettivamente si chiama programmazione cult del carico . È una pratica sorprendentemente diffusa. Fondamentalmente non funziona.


19
Il tuo Equals fallisce quando x o y è nullo.
dzendras

4
@dzendras Lo stesso per GetHashCode. Tuttavia, nota che la documentazione diIEqualityComparer<T> non specifica cosa fare con gli nullargomenti, ma nullneanche gli esempi forniti nell'articolo gestiscono .
Konrad Rudolph

49
Wow. L'abominio è inutilmente duro. Siamo qui per aiutarci a vicenda, non per insultarci. Immagino che faccia ridere alcune persone, ma consiglierei di rimuoverlo.
Jess

4
+1 per avermi fatto leggere della "programmazione del culto del carico" su wiki e poi aver cambiato il mio slogan skype in "// La magia profonda inizia qui ... seguita da un po 'di magia pesante".
Alex

4
@ NeilBenn Confondi un consiglio franco per scortesia. Dal momento che OP ha accettato la risposta (e, potrei notare, in una versione molto più severa!), Non sembra che abbiano commesso lo stesso errore. Non so perché pensi che dare consigli sia scortese, ma ti sbagli quando dici che "il ragazzo non ha bisogno di una lezione". Non sono assolutamente d'accordo: la conferenza era necessaria ed è stata presa a cuore. Il codice, come scritto, era cattivo e basato su cattive pratiche di lavoro. Non farlo notare sarebbe un disservizio e per niente utile, dal momento che l'OP non potrebbe migliorare il loro funzionamento.
Konrad Rudolph

47

Prova questo codice:

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

Un esempio del suo utilizzo sarebbe

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

19
GetHashCodedeve usare anche l'espressione: return _expr.Invoke(obj).GetHashCode();vedere questo post per un utilizzo.
orad

3

Solo codice, con implementazione GetHashCodee NULLconvalida:

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

Esempio: elenco di Class_reglement distinti da Numf

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

2

L'inclusione della tua classe di confronto (o più specificamente la AsEnumerablechiamata che dovevi usare per farlo funzionare) significava che la logica di ordinamento era passata dall'essere basata sul server del database a essere sul client del database (la tua applicazione). Ciò significa che il tuo client deve ora recuperare e quindi elaborare un numero maggiore di record, il che sarà sempre meno efficiente rispetto all'esecuzione della ricerca nel database in cui è possibile utilizzare gli indici appropriati.

Si dovrebbe invece provare a sviluppare una clausola where che soddisfi i propri requisiti, vedere Utilizzo di un IEqualityComparer con una clausola LINQ to Entities Except per ulteriori dettagli.


2

Se vuoi una soluzione generica senza boxe:

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

utilizzo:

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

0

IEquatable<T> può essere un modo molto più semplice per farlo con i framework moderni.

Ottieni una bella bool Equals(T other)funzione semplice e non c'è bisogno di scherzare con il casting o la creazione di una classe separata.

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

Nota che devi implementarlo GetHashCodese lo usi in un dizionario o con qualcosa di simile Distinct.

PS. Non penso che alcun metodo Equals personalizzato funzioni con il framework dell'entità direttamente sul lato database (penso che tu lo sappia perché fai AsEnumerable) ma questo è un metodo molto più semplice per fare un semplice Equals per il caso generale.

Se le cose non sembrano funzionare (come errori di chiave duplicati quando si esegue ToDictionary) inserire un punto di interruzione all'interno di Equals per assicurarsi che venga raggiunto e assicurarsi di averlo GetHashCodedefinito (con la parola chiave override).


1
Devi ancora verificare la presenza di null
disklosr

Non mi sono mai imbattuto in questo, ma mi ricorderò di farlo la prossima volta. Hai un null in un List <T> o qualcosa del genere?
Simon_Weaver

1
Con il .Equals()metodo che sembra tu abbia confrontato other.Hometowncon se stesso, invece dithis.Hometown
Jake Stokes,

Ops. Corretto errore di battitura :)
Simon_Weaver
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.