Distinto por propiedad de clase con LINQ

Tengo una colección:

List cars = new List(); 

Los automóviles están identificados de forma única por su propiedad CarCode .

Tengo tres autos en la colección y dos con CarCodes idénticos.

¿Cómo puedo usar LINQ para convertir esta colección en Cars con CarCodes únicos?

Puede usar la agrupación y obtener el primer automóvil de cada grupo:

 List distinct = cars .GroupBy(car => car.CarCode) .Select(g => g.First()) .ToList(); 

Use MoreLINQ , que tiene un método DistinctBy 🙂

 IEnumerable distinctCars = cars.DistinctBy(car => car.CarCode); 

(Esto es solo para LINQ to Objects, fíjate).

Mismo enfoque que Guffa pero como método de extensión:

 public static IEnumerable DistinctBy(this IEnumerable items, Func property) { return items.GroupBy(property).Select(x => x.First()); } 

Usado como:

 var uniqueCars = cars.DistinctBy(x => x.CarCode); 

Puede implementar un IEqualityComparer y usarlo en su extensión Distinct.

 class CarEqualityComparer : IEqualityComparer { #region IEqualityComparer Members public bool Equals(Car x, Car y) { return x.CarCode.Equals(y.CarCode); } public int GetHashCode(Car obj) { return obj.CarCode.GetHashCode(); } #endregion } 

Y entonces

 var uniqueCars = cars.Distinct(new CarEqualityComparer()); 

Otro método de extensión para Linq-to-Objects, sin usar GroupBy:

  ///  /// Returns the set of items, made distinct by the selected value. ///  /// The type of the source. /// The type of the result. /// The source collection. /// A function that selects a value to determine unique results. /// IEnumerable<TSource>. public static IEnumerable Distinct(this IEnumerable source, Func selector) { HashSet set = new HashSet(); foreach(var item in source) { var selectedValue = selector(item); if (set.Add(selectedValue)) yield return item; } } 

Creo que la mejor opción en términos de rendimiento (o en cualquier término) es Distinct utilizando la interfaz de IEqualityComparer .

Aunque se implementa cada vez que un nuevo comparador para cada clase es engorroso y produce un código repetitivo.

Así que aquí hay un método de extensión que produce un nuevo IEqualityComparer sobre la marcha para cualquier clase que use la reflexión.

Uso:

 var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray(); 

Código de método de extensión

 public static class LinqExtensions { public static IEnumerable DistinctBy(this IEnumerable items, Func property) { GeneralPropertyComparer comparer = new GeneralPropertyComparer(property); return items.Distinct(comparer); } } public class GeneralPropertyComparer : IEqualityComparer { private Func expr { get; set; } public GeneralPropertyComparer (Func expr) { this.expr = expr; } public bool Equals(T left, T right) { var leftProp = expr.Invoke(left); var rightProp = expr.Invoke(right); if (leftProp == null && rightProp == null) return true; else if (leftProp == null ^ rightProp == null) return false; else return leftProp.Equals(rightProp); } public int GetHashCode(T obj) { var prop = expr.Invoke(obj); return (prop==null)? 0:prop.GetHashCode(); } } 

No puede usar Distinct efectiva en una colección de objetos (sin trabajo adicional). Explicaré por qué.

La documentación dice :

Utiliza el comparador de igualdad predeterminado, Default , para comparar valores.

Para objetos, eso significa que usa el método de ecuación predeterminado para comparar objetos ( fuente ). Eso está en su código hash. Y dado que sus objetos no implementan los métodos GetHashCode() e Equals , verificará en la referencia del objeto, que no son distintos.

Otra forma de lograr lo mismo …

 List distinticBy = cars .Select(car => car.CarCode) .Distinct() .Select(code => cars.First(car => car.CarCode == code)) .ToList(); 

Es posible crear un método de extensión para hacer esto de una manera más genérica. Sería interesante que alguien pudiera evaluar el rendimiento de este ‘DistinctBy’ frente al enfoque GroupBy.

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