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
?
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!
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);
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.
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;
}
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();
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);