web-dev-qa-db-fra.com

Filtrer Linq SAUF sur les propriétés

Cela peut paraître idiot, mais tous les exemples que j'ai trouvés pour utiliser Except dans linq utilisent deux listes ou tableaux de chaînes ou d'entiers uniquement et les filtrent en fonction des correspondances, par exemple:

var excludes = users.Except(matches);

Je souhaite utiliser exclude pour que mon code soit simple et bref, mais je n'arrive pas à savoir comment procéder:

class AppMeta
{
    public int Id { get; set; }
}

var excludedAppIds = new List<int> {2, 3, 5, 6};
var unfilteredApps = new List<AppMeta>
                         {
                           new AppMeta {Id = 1},
                           new AppMeta {Id = 2},
                           new AppMeta {Id = 3},
                           new AppMeta {Id = 4},
                           new AppMeta {Id = 5}
                         }

Comment puis-je obtenir une liste de AppMeta back qui filtre sur excludedAppIds?

35
Wesley

Essayez une simple requête où

var filtered = unfilteredApps.Where(i => !excludedAppIds.Contains(i.Id)); 

La méthode except utilise l'égalité, vos listes contiennent des objets de types différents, donc aucun des éléments qu'elles contiennent ne sera égal!

59
ColinE

La réponse de ColinE est simple et élégante. Si vos listes sont plus grandes et que la liste des applications exclues est triée, BinarySearch<T> peut s'avérer plus rapide que Contains.

EXEMPLE:

unfilteredApps.Where(i => excludedAppIds.BinarySearch(i.Id) < 0);
13
dotNET

J'utilise une méthode d'extension pour Except, qui vous permet de comparer des pommes avec des oranges à condition qu'elles aient toutes les deux quelque chose de commun qui puisse être utilisé pour les comparer, comme un identifiant ou une clé.

public static class ExtensionMethods
{
    public static IEnumerable<TA> Except<TA, TB, TK>(
        this IEnumerable<TA> a,
        IEnumerable<TB> b,
        Func<TA, TK> selectKeyA,
        Func<TB, TK> selectKeyB, 
        IEqualityComparer<TK> comparer = null)
    {
        return a.Where(aItem => !b.Select(bItem => selectKeyB(bItem)).Contains(selectKeyA(aItem), comparer));
    }
}

puis utilisez-le comme ceci:

var filteredApps = unfilteredApps.Except(excludedAppIds, a => a.Id, b => b);

cette extension est très similaire à la réponse de ColinE, elle est simplement empaquetée dans une extension nette qui peut être réutilisée sans trop de charge mentale.

12
Scott

C'est ce dont LINQ a besoin

public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKey) 
{
    return from item in items
            join otherItem in other on getKey(item)
            equals getKey(otherItem) into tempItems
            from temp in tempItems.DefaultIfEmpty()
            where ReferenceEquals(null, temp) || temp.Equals(default(T))
            select item; 
}
11
azuneca

Construisez un List<AppMeta> à partir de la liste des exclus et utilisez l'opérateur Sauf Linq.

var ex = excludedAppIds.Select(x => new AppMeta{Id = x}).ToList();                           
var result = ex.Except(unfilteredApps).ToList();
7
scartag

J'aime les méthodes d’extension Except, mais la question initiale ne dispose pas d’un accès par clé symétrique et je préfère joindre (ou la variante Toute) à la jointure; c’est pourquoi nous devons tous nos crédits à la réponse de azuneca :

public static IEnumerable<T> Except<T, TKey>(this IEnumerable<TKey> items,
    IEnumerable<T> other, Func<T, TKey> getKey) {

    return from item in items
        where !other.Contains(getKey(item))
        select item;
}

Ce qui peut alors être utilisé comme:

var filteredApps = unfilteredApps.Except(excludedAppIds, ua => ua.Id);

De plus, cette version permet d’avoir besoin d’un mappage pour l’exception IEnumerable en utilisant un Select:

var filteredApps = unfilteredApps.Except(excludedApps.Select(a => a.Id), ua => ua.Id);
1
NetMage