833 votes

Distinct() avec lambda ?

Bon, alors j'ai un énumérable et je souhaite obtenir des valeurs distinctes de celui-ci.

Utilisation de System.Linq il y a, bien sûr, une méthode d'extension appelée Distinct . Dans le cas simple, il peut être utilisé sans paramètres, comme :

var distinctValues = myStringList.Distinct();

C'est bien, mais si j'ai un énumérateur d'objets pour lequel je dois spécifier l'égalité, la seule surcharge disponible est :

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

L'argument du comparateur d'égalité doit être une instance de IEqualityComparer<T> . Je peux le faire, bien sûr, mais c'est un peu verbeux et, en fait, maladroit.

Ce que j'aurais attendu est une surcharge qui prendrait un lambda, disons un Func<T, T, bool> :

var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

Quelqu'un sait-il si une telle extension existe, ou une solution de contournement équivalente ? Ou est-ce que je rate quelque chose ?

Alternativement, existe-t-il un moyen de spécifier un IEqualityComparer en ligne (m'embarrasser) ?

Mise à jour

J'ai trouvé une réponse d'Anders Hejlsberg à une poste dans un forum MSDN sur ce sujet. Il dit :

Le problème que vous allez rencontrer est que lorsque deux objets se comparent identiques, ils doivent avoir la même valeur de retour GetHashCode (sinon la table de hachage utilisée en interne par Distinct ne fonctionnera pas correctement). Nous utilisons IEqualityComparer car il propose des implémentations compatibles de Equals et GetHashCode. compatibles de Equals et GetHashCode dans une seule interface.

Je suppose que c'est logique.

2 votes

Voir stackoverflow.com/questions/1183403/ pour une solution utilisant GroupBy

19 votes

Merci pour la mise à jour sur Anders Hejlsberg !

0 votes

Non, cela n'a pas de sens - comment deux objets qui contiennent des valeurs identiques peuvent-ils renvoyer deux codes de hachage différents ?

1123voto

Carlo Bos Points 4948
IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

18 votes

Excellent ! Il est très facile d'encapsuler ceci dans une méthode d'extension, comme par exemple DistinctBy (ou même Distinct puisque la signature sera unique).

3 votes

Cela ne fonctionne pas pour moi ! <La méthode 'First' ne peut être utilisée que comme une opération de requête finale. Envisagez plutôt d'utiliser la méthode 'FirstOrDefault' dans cette instance.> Même si j'ai essayé 'FirstOrDefault', cela n'a pas fonctionné.

74 votes

@TorHaugen : Sachez juste qu'il y a un coût impliqué dans la création de tous ces groupes. Cela ne peut pas streamer l'entrée, et finira par mettre en mémoire tampon toutes les données avant de retourner quoi que ce soit. Cela peut ne pas être pertinent pour votre situation bien sûr, mais je préfère l'élégance de DistinctBy :)

547voto

Jon Skeet Points 692016

Il me semble que vous voulez DistinctBy de PlusLINQ . Vous pouvez alors écrire :

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

Voici une version abrégée de DistinctBy (pas de contrôle de nullité et pas d'option pour spécifier votre propre comparateur de clés) :

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

17 votes

Je savais que la meilleure réponse serait postée par Jon Skeet simplement en lisant le titre du post. Si cela a quelque chose à voir avec LINQ, Skeet est votre homme. Lisez 'C# In Depth' pour atteindre une connaissance de LINQ digne de Dieu.

2 votes

Excellente réponse ! !! également, pour tous les plaignants de VB_Complainers au sujet de la yield + librairie supplémentaire, foreach peut être réécrit comme suit return source.Where(element => knownKeys.Add(keySelector(element)));

1 votes

Je reçois une exception stackoverflow.com/questions/13405568/ lorsque j'utilise ce DistinctBy dans une requête LinqToSQL, le code ci-dessous fonctionne pour moi. public static IEnumerable<T> DistinctBy<T>(this IEnumerable<T> list, Func<T, object> propertySelector) { return list.GroupBy(propertySelector).Select(x => x.FirstOrDefault()) ; }

23voto

Arasu RRK Points 81

Solution abrégée

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

1 votes

Pourriez-vous ajouter une explication sur la raison de cette amélioration ?

0 votes

Cela a bien fonctionné pour moi, alors que ce n'était pas le cas pour Konrad.

21voto

JaredPar Points 333733

Non, il n'existe pas de surcharge de méthode d'extension pour cela. J'ai moi-même trouvé cela frustrant dans le passé et c'est pourquoi j'écris généralement une classe d'aide pour gérer ce problème. L'objectif est de convertir un Func<T,T,bool> a IEqualityComparer<T,T> .

Exemple

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

Cela vous permet d'écrire ce qui suit

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));

9 votes

Cependant, l'implémentation du code de hachage n'est pas très bonne. Il est plus facile de créer un IEqualityComparer<T> à partir d'une projection : stackoverflow.com/questions/188120/

7 votes

(Juste pour expliquer mon commentaire sur le code de hachage - il est très facile avec ce code de se retrouver avec Equals(x, y) == true, mais GetHashCode(x) != GetHashCode(y). Cela casse fondamentalement tout ce qui ressemble à une table de hachage).

0 votes

Je suis d'accord avec l'objection du code de hachage. Quand même, +1 pour le modèle.

13voto

Gordon Freeman Points 99

Il fera ce que vous voulez, mais je ne sais pas ce qu'il en est des performances :

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

Au moins, il n'est pas verbeux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X