LINQ’s Distinct () su una particolare proprietà

Sto giocando con LINQ per conoscerlo, ma non riesco a capire come usare Distinct quando non ho una lista semplice (una semplice lista di interi è abbastanza facile da fare, questa non è la domanda). Cosa devo usare Distinct su un elenco di oggetti su una o più proprietà dell’object?

Esempio: se un object è Person , con Id proprietà. Come posso ottenere tutte le Persone e usare Distinct su di esse con l’ Id di proprietà dell’object?

 Person1: Id=1, Name="Test1" Person2: Id=1, Name="Test1" Person3: Id=2, Name="Test2" 

Come posso ottenere solo Person1 e Person3? È ansible?

Se non è ansible con LINQ, quale sarebbe il modo migliore per avere una lista di Person seconda di alcune delle sue proprietà in .NET 3.5?

EDIT : questo è ora parte di MoreLINQ .

Ciò di cui hai bisogno è un “distinto” efficace. Non credo che faccia parte di LINQ così com’è, anche se è abbastanza facile scrivere:

 public static IEnumerable DistinctBy (this IEnumerable source, Func keySelector) { HashSet seenKeys = new HashSet(); foreach (TSource element in source) { if (seenKeys.Add(keySelector(element))) { yield return element; } } } 

Quindi, per trovare i valori distinti utilizzando solo la proprietà Id , è ansible utilizzare:

 var query = people.DistinctBy(p => p.Id); 

E per utilizzare più proprietà, puoi utilizzare tipi anonimi, che implementano l’uguaglianza in modo appropriato:

 var query = people.DistinctBy(p => new { p.Id, p.Name }); 

Non testato, ma dovrebbe funzionare (e ora almeno compila).

Tuttavia, presuppone il confronto predefinito per le chiavi: se si desidera passare a un confronto di uguaglianza, basta passarlo al costruttore HashSet .

Cosa succede se voglio ottenere un elenco distinto basato su una o più proprietà?

Semplice! Vuoi raggrupparli e scegliere un vincitore fuori dal gruppo.

 List distinctPeople = allPeople .GroupBy(p => p.PersonId) .Select(g => g.First()) .ToList(); 

Se si desidera definire i gruppi su più proprietà, ecco come:

 List distinctPeople = allPeople .GroupBy(p => new {p.PersonId, p.FavoriteColor} ) .Select(g => g.First()) .ToList(); 

Puoi anche utilizzare la syntax della query se vuoi che assomigli a tutti i tipi di LINQ:

 var uniquePeople = from p in people group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever} into mygroup select mygroup.FirstOrDefault(); 

Penso che sia abbastanza:

 list.Select(s => s.MyField).Distinct(); 

Uso:

 List pList = new List(); /* Fill list */ var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstorDefault()); 

Il punto in where ti aiuta a filtrare le voci (potrebbe essere più complesso) e il groupby e select eseguire la funzione distinta.

Puoi farlo con lo standard Linq.ToLookup() . Questo creerà una raccolta di valori per ogni chiave univoca. Basta selezionare il primo object della collezione

 Persons.ToLookup(p => p.Id).Select(coll => coll.First()); 

Soluzione prima raggruppare i campi, quindi selezionare l’elemento firstordefault.

  List distinctPeople = allPeople .GroupBy(p => p.PersonId) .Select(g => g.FirstOrDefault()) .ToList(); 

Il seguente codice è funzionalmente equivalente alla risposta di Jon Skeet .

Testato su .NET 4.5, dovrebbe funzionare su qualsiasi versione precedente di LINQ.

 public static IEnumerable DistinctBy( this IEnumerable source, Func keySelector) { HashSet seenKeys = new HashSet(); return source.Where(element => seenKeys.Add(keySelector(element))); } 

Per inciso, controlla l’ultima versione di DistinctBy.cs di Jon Skeet su Google Code .

Ho scritto un articolo che spiega come estendere la funzione Distinct in modo che tu possa fare quanto segue:

 var people = new List(); people.Add(new Person(1, "a", "b")); people.Add(new Person(2, "c", "d")); people.Add(new Person(1, "a", "b")); foreach (var person in people.Distinct(p => p.ID)) // Do stuff with unique list here. 

Ecco l’articolo: Estensione LINQ – Specifica di una proprietà nella funzione distinta

Nel caso in cui sia necessario un metodo Distinto su più proprietà, è ansible controllare la mia libreria PowerfulExtensions . Attualmente è in una fase molto giovane, ma già puoi usare metodi come Distinct, Union, Intersect, Tranne su un numero qualsiasi di proprietà;

Ecco come lo usi:

 using PowerfulExtensions.Linq; ... var distinct = myArray.Distinct(x => xA, x => xB); 

Puoi farlo (anche se non fulmineo velocemente) in questo modo:

 people.Where(p => !people.Any(q => (p != q && p.Id == q.Id))); 

Cioè, “seleziona tutte le persone dove non c’è un’altra persona diversa nella lista con lo stesso ID”.

Intendiamoci, nel tuo esempio, che sceglierei solo la persona 3. Non sono sicuro di come dire quale vuoi, tra i due precedenti.

Personalmente uso la seguente class:

 public class LambdaEqualityComparer : IEqualityComparer { private Func _selector; public LambdaEqualityComparer(Func selector) { _selector = selector; } public bool Equals(TSource obj, TSource other) { return _selector(obj).Equals(_selector(other)); } public int GetHashCode(TSource obj) { return _selector(obj).GetHashCode(); } } 

Quindi, un metodo di estensione:

 public static IEnumerable Distinct( this IEnumerable source, Func selector) { return source.Distinct(new LambdaEqualityComparer(selector)); } 

Infine, l’uso previsto:

 var dates = new List() { /* ... */ } var distinctYears = dates.Distinct(date => date.Year); 

Il vantaggio che ho trovato utilizzando questo approccio è il riutilizzo della class LambdaEqualityComparer per altri metodi che accettano LambdaEqualityComparer IEqualityComparer . (Oh, e lascio la roba di yield all’attuazione LINQ originale …)

Quando abbiamo affrontato un compito del genere nel nostro progetto, abbiamo definito una piccola API per comporre comparatori.

Quindi, il caso d’uso era così:

 var wordComparer = KeyEqualityComparer.Null(). ThenBy(item => item.Text). ThenBy(item => item.LangID); ... source.Select(...).Distinct(wordComparer); 

E l’API stessa si presenta così:

 using System; using System.Collections; using System.Collections.Generic; public static class KeyEqualityComparer { public static IEqualityComparer Null() { return null; } public static IEqualityComparer EqualityComparerBy( this IEnumerable source, Func keyFunc) { return new KeyEqualityComparer(keyFunc); } public static KeyEqualityComparer ThenBy( this IEqualityComparer equalityComparer, Func keyFunc) { return new KeyEqualityComparer(keyFunc, equalityComparer); } } public struct KeyEqualityComparer: IEqualityComparer { public KeyEqualityComparer( Func keyFunc, IEqualityComparer equalityComparer = null) { KeyFunc = keyFunc; EqualityComparer = equalityComparer; } public bool Equals(T x, T y) { return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) && EqualityComparer.Default.Equals(KeyFunc(x), KeyFunc(y)); } public int GetHashCode(T obj) { var hash = EqualityComparer.Default.GetHashCode(KeyFunc(obj)); if (EqualityComparer != null) { var hash2 = EqualityComparer.GetHashCode(obj); hash ^= (hash2 << 5) + hash2; } return hash; } public readonly Func KeyFunc; public readonly IEqualityComparer EqualityComparer; } 

Maggiori dettagli sono sul nostro sito: IEqualityComparer in LINQ .

Il modo migliore per fare ciò che sarà compatibile con altre versioni di .NET è sovrascrivere Equals e GetHash per gestirlo (vedi Stack Overflow question Questo codice restituisce valori distinti, tuttavia, ciò che voglio è restituire una raccolta fortemente tipizzata in contrapposizione a un tipo anonimo ), ma se hai bisogno di qualcosa che è generico in tutto il codice, le soluzioni in questo articolo sono grandi.

 Listlst=new List var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct(); 

Se non si desidera aggiungere la libreria MoreLinq al progetto solo per ottenere la funzionalità DistinctBy , è ansible ottenere lo stesso risultato finale utilizzando l’overload del metodo Distinct di Linq che accetta un argomento di IEqualityComparer .

Si inizia creando una class di confronto di uguaglianza personalizzata generica che utilizza la syntax lambda per eseguire il confronto personalizzato di due istanze di una class generica:

 public class CustomEqualityComparer : IEqualityComparer { Func _comparison; Func _hashCodeFactory; public CustomEqualityComparer(Func comparison, Func hashCodeFactory) { _comparison = comparison; _hashCodeFactory = hashCodeFactory; } public bool Equals(T x, T y) { return _comparison(x, y); } public int GetHashCode(T obj) { return _hashCodeFactory(obj); } } 

Quindi nel tuo codice principale lo usi in questo modo:

 Func areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id); Func getHashCode = (p) => p.Id.GetHashCode(); var query = people.Distinct(new CustomEqualityComparer(areEqual, getHashCode)); 

Ecco! 🙂

Quanto sopra assume quanto segue:

  • Proprietà Person.Id è di tipo int
  • La raccolta people non contiene alcun elemento nullo

Se la raccolta può contenere valori null, è sufficiente riscrivere i lambda per verificare la presenza di null, ad esempio:

 Func areEqual = (p1, p2) => { return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false; }; 

MODIFICARE

Questo approccio è simile a quello della risposta di Vladimir Nesterovsky, ma più semplice.

È anche simile a quello della risposta di Joel, ma consente una logica di confronto complessa che coinvolge più proprietà.

Tuttavia, se i tuoi oggetti possono essere sempre diversi Id allora un altro utente ha dato la risposta corretta che tutto ciò che devi fare è sovrascrivere le implementazioni predefinite di GetHashCode() e Equals() nella tua class Person e quindi usare solo l’out-of- the-box Distinct() metodo di Linq per filtrare eventuali duplicati.

Dovresti essere in grado di eseguire l’override di Equals di persona per eseguire effettivamente Equals su Person.id. Questo dovrebbe comportare il comportamento che stai cercando.

Si prega di fare una prova con il codice qui sotto.

 var Item = GetAll().GroupBy(x => x .Id).ToList();