Distinct de LINQ () en una propiedad particular

Estoy jugando con LINQ para aprender sobre esto, pero no puedo entender cómo usar Distinct cuando no tengo una lista simple (una simple lista de enteros es bastante fácil de hacer, esta no es la pregunta). ¿Qué deseo si quiero usar Distinct en una lista de un Objeto en una o más propiedades del objeto?

Ejemplo: si un objeto es Person , con Property Id . ¿Cómo puedo obtener toda la Persona y usar Distinct en ellas con la Id de propiedad del objeto?

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

¿Cómo puedo obtener solo Person1 y Person3? ¿Es eso posible?

Si no es posible con LINQ, ¿cuál sería la mejor manera de tener una lista de Person función de algunas de sus propiedades en .NET 3.5?

EDITAR : Esto ahora es parte de MoreLINQ .

Lo que necesita es un “distinct-by” eficazmente. No creo que sea parte de LINQ tal como está, aunque es bastante fácil de escribir:

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

Para encontrar los distintos valores usando solo la propiedad Id , puede usar:

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

Y para usar propiedades múltiples, puede usar tipos anónimos, que implementan la igualdad de manera apropiada:

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

No probado, pero debería funcionar (y ahora al menos comstack).

Sin embargo, asume el comparador predeterminado para las claves: si desea pasar un comparador de igualdad, simplemente páselo al constructor HashSet .

¿Qué pasa si quiero obtener una lista distinta basada en una o más propiedades?

¡Sencillo! Desea agruparlos y elegir un ganador del grupo.

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

Si desea definir grupos en propiedades múltiples, aquí se muestra cómo:

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

También puede usar la syntax de consulta si desea que se vea como LINQ-like:

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

Creo que es suficiente

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

Utilizar:

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

El where ayuda a filtrar las entradas (podría ser más complejo) y el groupby y select realizar la función distinta.

Puede hacer esto con el estándar Linq.ToLookup() . Esto creará una colección de valores para cada clave única. Solo selecciona el primer elemento de la colección

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

Primero, agrupe la solución por sus campos y luego seleccione el elemento firstordefault.

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

El siguiente código es funcionalmente equivalente a la respuesta de Jon Skeet .

Probado en .NET 4.5, debería funcionar en cualquier versión anterior de LINQ.

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

De manera confidencial, consulte la última versión de DistinctBy.cs de Jon Skeet en Google Code .

Escribí un artículo que explica cómo extender la función Distinct para que pueda hacer lo siguiente:

 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. 

Aquí está el artículo: Extendiendo LINQ – Especificando una propiedad en la función Distinct

En caso de que necesite un método Distinto en múltiples propiedades, puede consultar mi biblioteca PowerfulExtensions . Actualmente se encuentra en una etapa muy joven, pero ya puedes usar métodos como Distinct, Union, Intersect, excepto en cualquier cantidad de propiedades;

Así es como lo usas:

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

Puedes hacerlo (aunque no sea un rayo rápido) así:

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

Es decir, “seleccione todas las personas donde no haya otra persona diferente en la lista con la misma ID”.

Eso sí, en su ejemplo, eso simplemente seleccionaría a la persona 3. No estoy seguro de cómo decir cuál de los dos anteriores.

Personalmente utilizo la siguiente clase:

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

Entonces, un método de extensión:

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

Finalmente, el uso previsto:

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

La ventaja que encontré al utilizar este enfoque es la reutilización de la clase LambdaEqualityComparer para otros métodos que aceptan un IEqualityComparer . (Ah, y dejo las cosas de yield a la implementación LINQ original …)

Cuando enfrentamos tal tarea en nuestro proyecto definimos una pequeña API para componer comparadores.

Entonces, el caso de uso fue así:

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

Y la API en sí tiene este aspecto:

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

Más detalles están en nuestro sitio: IEqualityComparer en LINQ .

La mejor manera de hacer esto que será compatible con otras versiones de .NET es anular Equals y GetHash para manejar esto (ver Pregunta de desbordamiento de stack Este código arroja valores distintos. Sin embargo, lo que quiero es devolver una colección fuertemente tipada en lugar de un tipo anónimo ), pero si necesita algo que sea genérico en todo su código, las soluciones de este artículo son geniales.

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

Si no desea agregar la biblioteca MoreLinq a su proyecto solo para obtener la funcionalidad DistinctBy , entonces puede obtener el mismo resultado final usando la sobrecarga del método Distinct de Linq que toma en un argumento IEqualityComparer .

Comienza por crear una clase comparador de igualdad personalizada genérica que utiliza la syntax lambda para realizar una comparación personalizada de dos instancias de una clase genérica:

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

Luego en tu código principal lo usas así:

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

Voila! 🙂

Lo anterior asume lo siguiente:

  • Property Person.Id es de tipo int
  • La colección de people no contiene ningún elemento nulo

Si la colección puede contener valores nulos, simplemente reescriba el archivo lambdas para comprobar si no es válido, por ejemplo:

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

EDITAR

Este enfoque es similar al de la respuesta de Vladimir Nesterovsky pero más simple.

También es similar al de la respuesta de Joel, pero permite una lógica de comparación compleja que involucra propiedades múltiples.

Sin embargo, si sus objetos solo pueden diferir en Id entonces otro usuario dio la respuesta correcta de que todo lo que necesita hacer es anular las implementaciones predeterminadas de GetHashCode() y Equals() en su clase Person y luego simplemente usar el out-of- the-box Distinct() método de Linq para filtrar cualquier duplicado.

Debería poder anular Equals en persona para hacer Equals en Person.id. Esto debería dar como resultado el comportamiento que buscas.

Por favor, prueba con el siguiente código.

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