2010-12-14 52 views
9

Jusqu'à récemment, j'utilisais un objet Distinct dans LINQ pour sélectionner une catégorie distincte (une énumération) dans une table. Cela fonctionnait bien.Utilisation de Distinct avec LINQ et des objets

Je dois maintenant l'avoir distinct sur une classe contenant une catégorie et un pays (les deux enums). The Distinct ne fonctionne pas maintenant.

Qu'est-ce que je fais mal?

Répondre

13

Je crois que ce message explique votre problème: http://blog.jordanterrell.com/post/LINQ-Distinct()-does-not-work-as-expected.aspx

Le contenu du lien ci-dessus peut se résumer en disant que la méthode Distinct() peut être remplacé en procédant comme suit.

var distinctItems = items 
     .GroupBy(x => x.PropertyToCompare) 
     .Select(x => x.First()); 
+0

Cela a fonctionné très bien. Merci. –

+0

Veuillez ne pas répondre en liant simplement. Inclus le code pertinent dans la réponse. –

+1

Ouais, je ne fais plus ça mais en 2010 je n'ai pas fait attention à cette pratique. – Stilgar

4

essayer un IQualityComparer

public class MyObjEqualityComparer : IEqualityComparer<MyObj> 
{ 
    public bool Equals(MyObj x, MyObj y) 
    { 
     return x.Category.Equals(y.Category) && 
       x.Country.Equals(y.Country); 
    } 

    public int GetHashCode(MyObj obj) 
    { 
     return obj.GetHashCode(); 
    } 
} 

puis utilisez ici

var comparer = new MyObjEqualityComparer(); 
myObjs.Where(m => m.SomeProperty == "whatever").Distinct(comparer); 
+0

J'aime cette solution, mais j'ai eu un problème avec elle. Le GetHashCode l'a empêché de trouver les correspondances. J'ai dû le changer pour quelque chose comme 'return obj.Category.GetHashCode() + obj.Country.GetHashCode()' –

3

Pour plus d'explications, jetez un oeil à d'autres réponses. Je ne fais que fournir un moyen de gérer ce problème.

Vous aimerez this:

public class LambdaComparer<T>:IEqualityComparer<T>{ 
    private readonly Func<T,T,bool> _comparer; 
    private readonly Func<T,int> _hash; 
    public LambdaComparer(Func<T,T,bool> comparer): 
    this(comparer,o=>0) {} 
    public LambdaComparer(Func<T,T,bool> comparer,Func<T,int> hash){ 
    if(comparer==null) throw new ArgumentNullException("comparer"); 
    if(hash==null) throw new ArgumentNullException("hash"); 
    _comparer=comparer; 
    _hash=hash; 
    } 
    public bool Equals(T x,T y){ 
    return _comparer(x,y); 
    } 
    public int GetHashCode(T obj){ 
    return _hash(obj); 
    } 
} 

Utilisation:

public void Foo{ 
    public string Fizz{get;set;} 
    public BarEnum Bar{get;set;} 
} 

public enum BarEnum {One,Two,Three} 

var lst=new List<Foo>(); 
lst.Distinct(new LambdaComparer<Foo>(
    (x1,x2)=>x1.Fizz==x2.Fizz&& 
      x1.Bar==x2.Bar)); 

Vous pouvez même l'enrouler autour d'éviter d'écrire chose new LambdaComparer<T>(...) bruyante:

public static class EnumerableExtensions{ 
public static IEnumerable<T> SmartDistinct<T> 
    (this IEnumerable<T> lst, Func<T, T, bool> pred){ 
    return lst.Distinct(new LambdaComparer<T>(pred)); 
} 
} 

Utilisation:

lst.SmartDistinct((x1,x2)=>x1.Fizz==x2.Fizz&&x1.Bar==x2.Bar); 

NB: fonctionne de manière fiable que pour Linq2Objects

1

Vous ne le fait pas de mal, il est juste la mauvaise mise en œuvre de .Distinct() dans le .NET Framework.

Une façon de le réparer est déjà montrée dans les autres réponses, mais il y a aussi une solution plus courte disponible, qui a l'avantage que vous pouvez l'utiliser facilement comme méthode d'extension sans avoir à modifier le hachage de l'objet valeurs.

Jetez un oeil à ceci:


Utilisation:

var myQuery=(from x in Customers select x).MyDistinct(d => d.CustomerID); 

Note: Cet exemple utilise une requête de base de données, mais il ne fonctionne aussi avec une liste d'objets dénombrable.


Déclaration de MyDistinct:

public static class Extensions 
{ 
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query, 
                Func<T, V> f) 
    { 
     return query.GroupBy(f).Select(x=>x.First()); 
    } 
} 

Et cela fonctionne pour tout, des objets ainsi que des entités.Si nécessaire, vous pouvez créer une deuxième méthode d'extension surchargée pour IQueryable<T> en remplaçant simplement le type de retour et le premier type de paramètre dans l'exemple ci-dessus.