web-dev-qa-db-fra.com

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, ainsi:

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

Ma question est donc la suivante: Distinct utilisera-t-il dans ce cas l'objet par défaut est égal (ce qui me sera inutile, puisque chaque objet est nouveau) ou peut-il être demandé de faire un autre égal (dans ce cas, les valeurs égales de Alpha et Bravo => instances égales)? Y a-t-il un moyen d'obtenir ce résultat, si cela ne le fait pas?

144
GWLlosa

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

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

La réponse courte (et je cite):

Il s'avère que le compilateur C # remplace Equals et GetHashCode pour les types anonymes. L'implémentation des deux méthodes substituées utilise toutes les propriétés publiques du type pour calculer le code de hachage d'un objet et tester son é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 totalement sûr d’utiliser la méthode Distinct () sur une requête renvoyant des types anonymes.

178
Matt Hamilton
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é plus tôt

14
Nabin Nepal

Intéressant que cela fonctionne en C # mais pas en VB

Renvoie les 26 lettres:

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

Retours 52 ...

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

J'ai fait un petit test et constaté que si les propriétés sont des types valeur, cela semble fonctionner correctement. S'ils ne sont pas des types valeur, le type doit fournir ses propres implémentations Equals et GetHashCode pour que cela fonctionne. Je pense que les chaînes fonctionneraient.

4
tvanfosson

Vous pouvez créer votre propre méthode Distinct Extension qui prend l'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();
    }       
}

Puis créez votre méthode d'extension distincte

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 objets 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();
2
Nabin Nepal

Salut, j'ai le même problème et j'ai trouvé une solution. Vous devez implémenter l'interface IEquatable ou simplement substituer les méthodes (Equals & GetHashCode). Mais ce n'est pas le truc, le truc venant de la méthode GetHashCode. Vous ne devez pas renvoyer le code de hachage de l'objet de votre classe, mais vous devez également renvoyer le hachage de la propriété que vous souhaitez comparer.

public override bool Equals(object obj)
    {
        Person p = obj as Person;
        if ( obj == null )
            return false;
        if ( object.ReferenceEquals( p , this ) )
            return true;
        if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
            return true;
        return false;
        //return base.Equals( obj );
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }

Comme vous le voyez, j'ai une classe appelée personne qui a 3 propriétés (Nom, Age, IsEgyptien "Parce que je suis"). Dans le GetHashCode, j'ai renvoyé le hachage de la propriété Name et non de l'objet Person.

Essayez-le et cela fonctionnera ISA. Merci, Modather Sadik

0
Modather Sadik

Pour que cela fonctionne dans VB.NET, vous devez spécifier le mot clé Key avant chaque propriété du type anonyme, comme suit:

myObjectCollection.Select(Function(item) New With
{
    Key .Alpha = item.propOne,
    Key .Bravo = item.propTwo
}).Distinct()

Je me débattais avec ceci, je pensais que VB.NET ne supportait pas ce type de fonctionnalité, mais en réalité, il le fait.

0
Alisson

Si Alpha et Bravo héritent tous deux d'une classe commune, vous pourrez dicter le contrôle d'égalité dans la classe parente en implémentant IEquatable<T>.

Par exemple:

public class CommonClass : IEquatable<CommonClass>
{
    // needed for Distinct()
    public override int GetHashCode() 
    {
        return base.GetHashCode();
    }

    public bool Equals(CommonClass other)
    {
        if (other == null) return false;
        return [equality test];
    }
}
0
ern