Filter Linq EXCEPT on properties

前端 未结 8 1292
南方客
南方客 2020-12-04 17:45

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

相关标签:
8条回答
  • 2020-12-04 18:09

    ColinE's answer is simple and elegant. If your lists are larger and provided that the excluded apps list is sorted, BinarySearch<T> may prove faster than Contains.

    EXAMPLE:

    unfilteredApps.Where(i => excludedAppIds.BinarySearch(i.Id) < 0);
    
    0 讨论(0)
  • 2020-12-04 18:10

    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!

    0 讨论(0)
  • 2020-12-04 18:10

    This is what LINQ needs

    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; 
    }
    
    0 讨论(0)
  • 2020-12-04 18:12
    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);
    
    0 讨论(0)
  • 2020-12-04 18:25

    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:

    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:

    var filteredApps = unfilteredApps.Except(excludedApps.Select(a => a.Id), ua => ua.Id);
    
    0 讨论(0)
  • 2020-12-04 18:26

    MoreLinq has something useful for this 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);
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题