在属性上筛选Linq EXCEPT
本文关键字:Linq EXCEPT 筛选 属性 | 更新日期: 2023-09-27 18:04:55
这可能看起来很傻,但我在linq中发现的所有使用Except
的示例都使用了两个仅包含字符串或整数的列表或数组,并根据匹配项对其进行筛选,例如:
var excludes = users.Except(matches);
我想使用exclude来保持我的代码简短,但似乎找不到如何做到以下几点:
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}
}
如何获取在excludedAppIds
上进行筛选的AppMeta
列表?
尝试一个简单的where查询
var filtered = unfilteredApps.Where(i => !excludedAppIds.Contains(i.Id));
except方法使用相等,您的列表包含不同类型的对象,因此它们包含的项都不相等!
ColinE的回答简洁而优雅。如果您的列表较大,并且排除的应用程序列表已排序,则BinarySearch<T>
可能比Contains
更快。
示例:
unfilteredApps.Where(i => excludedAppIds.BinarySearch(i.Id) < 0);
我为Except使用了一个扩展方法,该方法允许您比较苹果和橙子,只要它们都有可以用来比较的共同点,比如Id或Key。
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));
}
}
然后像这样使用它:
var filteredApps = unfilteredApps.Except(excludedAppIds, a => a.Id, b => b);
这个扩展与ColinE的答案非常相似,它只是被打包成一个整洁的扩展,可以重复使用,而不需要太多的脑力开销。
这就是LINQ需要的地方
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;
}
从排除的列表中构造一个List<AppMeta>
,并使用Except-Linq运算符。
var ex = excludedAppIds.Select(x => new AppMeta{Id = x}).ToList();
var result = ex.Except(unfilteredApps).ToList();
由于.NET 6,现在有一个内置的ExceptBy
专门为此设计:
var filtered = unfilteredApps.ExceptBy(excludedAppIds, a => a.Id);
我喜欢Except扩展方法,但最初的问题没有对称密钥访问,我更喜欢Contains(或Any变体(来加入,所以所有的功劳都归功于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;
}
然后可以像这样使用:
var filteredApps = unfilteredApps.Except(excludedAppIds, ua => ua.Id);
此外,此版本允许使用Select:为异常IEnumerable进行映射
var filteredApps = unfilteredApps.Except(excludedApps.Select(a => a.Id), ua => ua.Id);
MoreLinq对此有一些有用的东西MoreLinq.Source.MoreEnumerable.ExceptBy
https://github.com/gsscoder/morelinq/blob/master/MoreLinq/ExceptBy.cs
namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Linq;
static partial class MoreEnumerable
{
/// <summary>
/// Returns the set of elements in the first sequence which aren't
/// in the second sequence, according to a given key selector.
/// </summary>
/// <remarks>
/// This is a set operation; if multiple elements in <paramref name="first"/> have
/// equal keys, only the first such element is returned.
/// This operator uses deferred execution and streams the results, although
/// a set of keys from <paramref name="second"/> is immediately selected and retained.
/// </remarks>
/// <typeparam name="TSource">The type of the elements in the input sequences.</typeparam>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="first">The sequence of potentially included elements.</param>
/// <param name="second">The sequence of elements whose keys may prevent elements in
/// <paramref name="first"/> from being returned.</param>
/// <param name="keySelector">The mapping from source element to key.</param>
/// <returns>A sequence of elements from <paramref name="first"/> whose key was not also a key for
/// any element in <paramref name="second"/>.</returns>
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
IEnumerable<TSource> second,
Func<TSource, TKey> keySelector)
{
return ExceptBy(first, second, keySelector, null);
}
/// <summary>
/// Returns the set of elements in the first sequence which aren't
/// in the second sequence, according to a given key selector.
/// </summary>
/// <remarks>
/// This is a set operation; if multiple elements in <paramref name="first"/> have
/// equal keys, only the first such element is returned.
/// This operator uses deferred execution and streams the results, although
/// a set of keys from <paramref name="second"/> is immediately selected and retained.
/// </remarks>
/// <typeparam name="TSource">The type of the elements in the input sequences.</typeparam>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="first">The sequence of potentially included elements.</param>
/// <param name="second">The sequence of elements whose keys may prevent elements in
/// <paramref name="first"/> from being returned.</param>
/// <param name="keySelector">The mapping from source element to key.</param>
/// <param name="keyComparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <c>TSource</c> is used.</param>
/// <returns>A sequence of elements from <paramref name="first"/> whose key was not also a key for
/// any element in <paramref name="second"/>.</returns>
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
IEnumerable<TSource> second,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> keyComparer)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (keySelector == null) throw new ArgumentNullException("keySelector");
return ExceptByImpl(first, second, keySelector, keyComparer);
}
private static IEnumerable<TSource> ExceptByImpl<TSource, TKey>(this IEnumerable<TSource> first,
IEnumerable<TSource> second,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> keyComparer)
{
var keys = new HashSet<TKey>(second.Select(keySelector), keyComparer);
foreach (var element in first)
{
var key = keySelector(element);
if (keys.Contains(key))
{
continue;
}
yield return element;
keys.Add(key);
}
}
}
}
public static class ExceptByProperty
{
public static List<T> ExceptBYProperty<T, TProperty>(this List<T> list, List<T> list2, Expression<Func<T, TProperty>> propertyLambda)
{
Type type = typeof(T);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expresion '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
Func<T, TProperty> func = propertyLambda.Compile();
var ids = list2.Select<T, TProperty>(x => func(x)).ToArray();
return list.Where(i => !ids.Contains(((TProperty)propInfo.GetValue(i, null)))).ToList();
}
}
public class testClass
{
public int ID { get; set; }
public string Name { get; set; }
}
用于测试:
List<testClass> a = new List<testClass>();
List<testClass> b = new List<testClass>();
a.Add(new testClass() { ID = 1 });
a.Add(new testClass() { ID = 2 });
a.Add(new testClass() { ID = 3 });
a.Add(new testClass() { ID = 4 });
a.Add(new testClass() { ID = 5 });
b.Add(new testClass() { ID = 3 });
b.Add(new testClass() { ID = 5 });
a.Select<testClass, int>(x => x.ID);
var items = a.ExceptBYProperty(b, u => u.ID);