LINQ Seleziona Distinto con tipi anonimi


150

Quindi ho una collezione di oggetti. Il tipo esatto non è importante. Da esso voglio estrarre tutte le coppie uniche di una coppia di proprietà particolari, quindi:

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

Quindi la mia domanda è: Will Distinct in questo caso usa l'oggetto uguale a uguale (che sarà inutile per me, poiché ogni oggetto è nuovo) o può essere detto di fare un uguale diverso (in questo caso, valori uguali di Alpha e Bravo => istanze uguali)? C'è un modo per ottenere quel risultato, se ciò non lo fa?


Questo è LINQ-to-Objects o LINQ-to-SQL? Se solo oggetti, probabilmente sei sfortunato. Tuttavia, se L2S, potrebbe funzionare, poiché DISTINCT verrebbe passato all'istruzione SQL.
James Curran,

Risposte:


188

Leggi qui l'eccellente post di K. Scott Allen:

E uguaglianza per tutti ... tipi anonimi

La risposta breve (e cito):

Si scopre che il compilatore C # sostituisce Equals e GetHashCode per tipi anonimi. L'implementazione dei due metodi sostituiti utilizza tutte le proprietà pubbliche sul tipo per calcolare il codice hash di un oggetto e verificare l'uguaglianza. Se due oggetti dello stesso tipo anonimo hanno tutti gli stessi valori per le loro proprietà, gli oggetti sono uguali.

Quindi è assolutamente sicuro usare il metodo Distinct () su una query che restituisce tipi anonimi.


2
Questo è vero, penso, solo se le proprietà stesse sono tipi di valore o implementano l'uguaglianza di valore - vedi la mia risposta.
tvanfosson,

Sì, poiché utilizza GetHashCode su ciascuna proprietà, funzionerebbe solo se ogni proprietà avesse una propria implementazione unica. Penso che la maggior parte dei casi d'uso implicherebbe solo tipi semplici come proprietà, quindi è generalmente sicuro.
Matt Hamilton,

4
Si finisce per significare che l'uguaglianza di due dei tipi anonimi dipende dall'uguaglianza dei membri, il che va bene per me, dal momento che i membri sono definiti da qualche parte in cui posso arrivare e scavalcare l'uguaglianza se devo. Non volevo creare una classe per questo solo per sovrascrivere gli uguali.
GWLlosa,

3
Potrebbe valere la pena di presentare una petizione a MS per introdurre la sintassi "chiave" in C # di VB (dove è possibile specificare determinate proprietà di un tipo anonimo come "chiave primaria" - vedere il post sul blog a cui mi sono collegato).
Matt Hamilton,

1
Articolo molto interessante. Grazie!
Alexander Prokofyev,

14
public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

Ci scusiamo per la formattazione incasinata in precedenza


Questa estensione non può gestire il tipo objecte object. Se entrambi objectsono stringancora restituisce le righe duplicate. Prova FirstNameis isof objecte assegnalo con lo stesso stringlì.
CallMeLaNN,

Questa è un'ottima risposta per oggetti digitati ma non necessaria per tipi anonimi.
crokusek,

5

Interessante che funzioni in C # ma non in VB

Restituisce le 26 lettere:

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

Restituisce 52 ...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()

11
Se aggiungi la Keyparola chiave al tipo anonimo .Distinct(), funzionerà come previsto (ad es New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}.).
Cᴏʀʏ

3
Cory ha ragione. La traduzione corretta del codice C # new {A = b}è New {Key .A = b}. Le proprietà non chiave nelle classi VB anonime sono modificabili, motivo per cui vengono confrontate per riferimento. In C #, tutte le proprietà delle classi anonime sono immutabili.
Heinzi,

4

Ho eseguito un piccolo test e ho scoperto che se le proprietà sono tipi di valore, sembra funzionare bene. Se non sono tipi di valore, il tipo deve fornire le proprie implementazioni Equals e GetHashCode affinché funzioni. Le stringhe, penso, funzionerebbero.


2

È possibile creare il proprio metodo di estensione distinta che accetta l'espressione lambda. Ecco un esempio

Creare una classe che deriva dall'interfaccia IEqualityComparer

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

Quindi crea il tuo metodo di estensione distinta

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

e puoi usare questo metodo per trovare elementi distinti

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

Questa estensione non può gestire il tipo objecte object. Se entrambi objectsono stringancora restituisce le righe duplicate. Prova FirstNameis isof objecte assegnalo con lo stesso stringlì.
CallMeLaNN,

0

Se Alpha ed Bravoentrambi ereditano da una classe comune, sarai in grado di dettare il controllo dell'uguaglianza nella classe genitrice implementandoIEquatable<T> .

Per esempio:

public class CommonClass : IEquatable<CommonClass>
{
    // needed for Distinct()
    public override int GetHashCode() 
    {
        return base.GetHashCode();
    }

    public bool Equals(CommonClass other)
    {
        if (other == null) return false;
        return [equality test];
    }
}

quindi se usi come proprietà delle tue classi di tipi anonimi che implementano IEquatable <T>, viene chiamato Equals invece del comportamento predefinito (controllo di tutte le proprietà pubbliche tramite la riflessione?)
D_Guidi

0

Ehi, ho lo stesso problema e ho trovato una soluzione. Devi implementare l'interfaccia IEquatable o semplicemente sovrascrivere i metodi (Equals & GetHashCode). Ma questo non è il trucco, il trucco proveniente dal metodo GetHashCode. Non dovresti restituire il codice hash dell'oggetto della tua classe ma dovresti restituire l'hash della proprietà che vuoi confrontare in questo modo.

public override bool Equals(object obj)
    {
        Person p = obj as Person;
        if ( obj == null )
            return false;
        if ( object.ReferenceEquals( p , this ) )
            return true;
        if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
            return true;
        return false;
        //return base.Equals( obj );
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }

Come vedi ho una classe chiamata persona ho 3 proprietà (Nome, Età, IsEgyptian "Perché sono") Nel GetHashCode ho restituito l'hash della proprietà Name non l'oggetto Person.

Provalo e funzionerà ISA. Grazie Modather Sadik


1
GetHashCode dovrebbe utilizzare tutti gli stessi campi e proprietà utilizzati nel confronto per l'uguaglianza, non solo uno di essi. vale a direpublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG in SD

Per informazioni sulla generazione di un buon algoritmo hash: stackoverflow.com/questions/263400/…
JG in SD

0

Affinché funzioni in VB.NET, è necessario specificare la Keyparola chiave prima di ogni proprietà nel tipo anonimo, proprio come questo:

myObjectCollection.Select(Function(item) New With
{
    Key .Alpha = item.propOne,
    Key .Bravo = item.propTwo
}).Distinct()

Ero alle prese con questo, pensavo che VB.NET non supportasse questo tipo di funzionalità, ma in realtà lo fa.

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.