简体   繁体   English

过滤Linq EXCEPT属性

[英]Filter Linq EXCEPT on properties

This may seem silly, but all the examples I've found for using Except in linq use two lists or arrays of only strings or integers and filters them based on the matches, for example: 这可能看起来很愚蠢,但我发现在linq中使用Except所有示例都使用两个列表或仅包含字符串或整数的数组,并根据匹配过滤它们,例如:

var excludes = users.Except(matches);

I want to use exclude to keep my code short and simple, but can't seem to find out how to do the following: 我想使用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}
                         }

How do I get a list of AppMeta back that filters on excludedAppIds ? 如何获取在excludedAppIds上过滤的AppMeta列表?

Try a simple where query 尝试一个简单的查询位置

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

The except method uses equality, your lists contain objects of different types, so none of the items they contain will be equal! except方法使用相等,您的列表包含不同类型的对象,因此它们包含的任何项都不相同!

ColinE's answer is simple and elegant. ColinE的答案简单而优雅。 If your lists are larger and provided that the excluded apps list is sorted, BinarySearch<T> may prove faster than Contains . 如果您的列表较大并且排除了排除的应用列表,则BinarySearch<T>可能比Contains更快。

EXAMPLE: 例:

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

I use an extension method for Except, that allows you to compare Apples with Oranges as long as they both have something common that can be used to compare them, like an Id or Key. 我使用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));
    }
}

then use it something like this: 然后使用这样的东西:

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

the extension is very similar to ColinE 's answer, it's just packaged up into a neat extension that can be reused without to much mental overhead. 扩展与ColinE的答案非常相似,它只是打包成一个整齐的扩展,可以重复使用,而不需要太多的精神开销。

This is what LINQ needs 这就是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; 
}

Construct a List<AppMeta> from the excluded List and use the Except Linq operator. 从排除的List构造List<AppMeta>并使用Except Linq运算符。

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

I like the Except extension methods, but the original question doesn't have symmetric key access and I prefer Contains (or the Any variation) to join, so with all credit to azuneca's answer : 我喜欢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;
}

Which can then be used like: 然后可以使用它,如:

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

Also, this version allows for needing a mapping for the exception IEnumerable by using a Select: 此外,此版本允许通过使用Select来需要映射异常IEnumerable:

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

MoreLinq has something useful for this MoreLinq.Source.MoreEnumerable.ExceptBy MoreLinq对此MoreLinq.Source.MoreEnumerable.ExceptBy有用

https://github.com/gsscoder/morelinq/blob/master/MoreLinq/ExceptBy.cs 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; }
}

For Test this: 为了测试这个:

        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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM