J'ai une collection:
List<Car> cars = new List<Car>();
Les voitures sont identifiées de manière unique par leur propriété CarCode
.
J'ai trois voitures dans la collection et deux avec des codes de voiture identiques.
Comment utiliser LINQ pour convertir cette collection en voitures avec des codes de voiture uniques?
Vous pouvez utiliser le regroupement et obtenir la première voiture de chaque groupe:
List<Car> distinct =
cars
.GroupBy(car => car.CarCode)
.Select(g => g.First())
.ToList();
Utilisez MoreLINQ , qui a une méthode DistinctBy
:)
IEnumerable<Car> distinctCars = cars.DistinctBy(car => car.CarCode);
(Ceci est uniquement pour LINQ to Objects, remarquez.)
Même approche que Guffa mais comme méthode d'extension:
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
{
return items.GroupBy(property).Select(x => x.First());
}
Utilisé comme:
var uniqueCars = cars.DistinctBy(x => x.CarCode);
Vous pouvez implémenter un IEqualityComparer et l'utiliser dans votre extension Distinct.
class CarEqualityComparer : IEqualityComparer<Car>
{
#region IEqualityComparer<Car> Members
public bool Equals(Car x, Car y)
{
return x.CarCode.Equals(y.CarCode);
}
public int GetHashCode(Car obj)
{
return obj.CarCode.GetHashCode();
}
#endregion
}
Puis
var uniqueCars = cars.Distinct(new CarEqualityComparer());
Une autre méthode d'extension pour Linq-to-Objects, sans utiliser GroupBy:
/// <summary>
/// Returns the set of items, made distinct by the selected value.
/// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="source">The source collection.</param>
/// <param name="selector">A function that selects a value to determine unique results.</param>
/// <returns>IEnumerable<TSource>.</returns>
public static IEnumerable<TSource> Distinct<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
HashSet<TResult> set = new HashSet<TResult>();
foreach(var item in source)
{
var selectedValue = selector(item);
if (set.Add(selectedValue))
yield return item;
}
}
Je pense que la meilleure option en termes de performances (ou en termes quelconques) est de se distinguer en utilisant l’interface IEqualityComparer.
Bien qu’appliquer à chaque fois un nouveau comparateur pour chaque classe soit lourd et produit un code standard.
Donc, voici une méthode d’extension qui produit un nouveau IEqualityComparer à la volée pour toute classe utilisant la réflexion.
tilisation:
var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();
Code de méthode d'extension
public static class LinqExtensions
{
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
{
GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
return items.Distinct(comparer);
}
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
private Func<T, TKey> expr { get; set; }
public GeneralPropertyComparer (Func<T, TKey> 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();
}
}
Vous ne pouvez pas utiliser efficacement Distinct
sur une collection d'objets (sans travail supplémentaire). Je vais expliquer pourquoi.
Il utilise le comparateur d'égalité par défaut,
Default
, pour comparer les valeurs.
Pour les objets, cela signifie que la méthode d’équation par défaut est utilisée pour comparer les objets ( source ). C'est sur leur code de hachage. Et comme vos objets n’implémentent pas les méthodes GetHashCode()
et Equals
, il vérifie la référence de l’objet, qui ne sont pas distincts.
Une autre façon d'accomplir la même chose ...
List<Car> distinticBy = cars
.Select(car => car.CarCode)
.Distinct()
.Select(code => cars.First(car => car.CarCode == code))
.ToList();
Il est possible de créer une méthode d'extension pour le faire de manière plus générique. Il serait intéressant que quelqu'un évalue la performance de ce "DistinctBy" par rapport à l'approche GroupBy.
Vous pouvez consulter ma bibliothèque PowerfulExtensions . Actuellement, il est encore très jeune, mais vous pouvez déjà utiliser des méthodes telles que Distinct, Union, Intersect, Sauf sur un nombre quelconque de propriétés;
Voici comment vous l'utilisez:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);