158 votes

LINQ Select Distinct avec les types anonymes

J'ai donc une collection d'objets. Le type exact n'est pas important. Je veux en extraire toutes les paires uniques d'une paire de propriétés particulières, de la manière suivante :

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

Ma question est donc la suivante : dans ce cas, Distinct utilisera-t-il les égalités d'objets par défaut (ce qui me sera inutile, puisque chaque objet est nouveau) ou peut-on lui demander d'effectuer des égalités différentes (dans ce cas, valeurs égales d'Alpha et de Bravo => instances égales) ? Existe-t-il un autre moyen d'obtenir ce résultat, si cela ne fonctionne pas ?

0 votes

S'agit-il de LINQ-to-Objects ou de LINQ-to-SQL ? S'il s'agit uniquement d'objets, vous n'avez probablement pas de chance. Cependant, s'il s'agit de L2S, cela peut fonctionner, car le DISTINCT serait transmis à l'instruction SQL.

0 votes

197voto

Matt Hamilton Points 98268

Lisez l'excellent article de K. Scott Allen ici :

Et l'égalité pour tous... Types d'anonymes

La réponse courte (et je cite) :

Il s'avère que le compilateur C# remplace Equals et GetHashCode pour les types anonymes. anonymes. L'implémentation des deux méthodes surchargées utilise toutes les propriétés publiques du type pour calculer un code de hachage d'un objet et tester l'égalité l'égalité. Si deux objets du même type anonyme ont toutes les mêmes valeurs pour leurs propriétés - les objets sont égaux.

Il est donc tout à fait possible d'utiliser la méthode Distinct() sur une requête qui renvoie des types anonymes.

3 votes

Cela n'est vrai, je pense, que si les propriétés elles-mêmes sont des types de valeurs ou mettent en œuvre l'égalité des valeurs - voir ma réponse.

0 votes

Oui, puisqu'il utilise GetHashCode sur chaque propriété, cela ne fonctionnerait que si chaque propriété avait sa propre implémentation unique de cette fonction. Je pense que la plupart des cas d'utilisation n'impliquent que des types simples comme propriétés, donc c'est généralement sûr.

4 votes

Cela signifie que l'égalité de deux des types anonymes dépend de l'égalité des membres, ce qui me convient, puisque les membres sont définis à un endroit où je peux accéder et modifier l'égalité si nécessaire. Je ne voulais pas avoir à créer une classe pour cela, juste pour remplacer equals.

14voto

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

Désolé pour le formatage raté de tout à l'heure

5voto

GeorgeBarker Points 742

Il est intéressant que cela fonctionne en C# mais pas en VB.

Donne les 26 lettres :

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

Retourne 52...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()

12 votes

Si vous ajoutez le Key au type anonyme, le .Distinct() fonctionneront comme prévu (par exemple New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()} ).

3 votes

Cory a raison. La traduction correcte du code C# new {A = b} es New {Key .A = b} . Les propriétés non clés des classes anonymes VB sont mutables, c'est pourquoi elles sont comparées par référence. En C#, toutes les propriétés des classes anonymes sont immuables.

4voto

tvanfosson Points 268301

J'ai effectué un petit test et j'ai constaté que si les propriétés sont de type valeur, cela semble fonctionner correctement. Si ce ne sont pas des types de valeurs, alors le type doit fournir ses propres implémentations Equals et GetHashCode pour que cela fonctionne. Les chaînes de caractères, je pense, fonctionneraient.

2voto

Vous pouvez créer votre propre méthode Distinct Extension qui prend une expression lambda. Voici un exemple

Créer une classe qui dérive de l'interface IEqualityComparer

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

Créez ensuite votre méthode Distinct Extension

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

et vous pouvez utiliser cette méthode pour trouver des éléments distincts

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

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