web-dev-qa-db-fra.com

Distinct par propriété de classe avec LINQ

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?

147
user278618

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

Utilisez MoreLINQ , qui a une méthode DistinctBy :)

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

(Ceci est uniquement pour LINQ to Objects, remarquez.)

113
Jon Skeet

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);
44
Sheldor the conqueror

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());
28
Anthony Pegram

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&lt;TSource&gt;.</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;
        }
    }
7
Luke Puplett

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

Vous ne pouvez pas utiliser efficacement Distinct sur une collection d'objets (sans travail supplémentaire). Je vais expliquer pourquoi.

La documentation dit :

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.

2
Patrick Hofman

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.

1
JwJosefy

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);
1
Andrzej Gis