简体   繁体   中英

IEnumerable.Except() between different classes with a common field

Is it possible to use Except() for two List's that have two different classes but a common field? I have List<User1> and List<User2> collections. They have different properties except Id column and I want to find the different records between them using this Id column. I'm trying to use List<>.Except() but I'm getting this error:

The type arguments for method 'System.Linq.Enumerable.Except(System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerable)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Here's what I'm trying:

List<User1> list1 = List1();
List<User2> list2 = List2();
var listdiff = list1.Except(list2.Select(row => row.Id));

What am I doing wrong?

List1 contains instances of User1 and List2 contains instances of User2.

What type of instance should be produced by list1.Except(list2.Select(row => row.Id)) ? In other words if type inference was not available, what would you replace var with?

If User1 and User2 inherit from the same ancestor (with ID), use List<User> instead.

Otherwise:

var list2Lookup = list2.ToLookup(user => user.Id);
var listdiff = list1.Where(user => (!list2Lookup.Contains(user.Id))

Not Except , but the correct results and similar performance:

// assumes that the Id property is an Int32
var tempKeys = new HashSet<int>(list2.Select(x => x.Id));
var listdiff = list1.Where(x => tempKeys.Add(x.Id));

And, of course, you can wrap it all up in your own re-usable extension method:

var listdiff = list1.Except(list2, x => x.Id, y => y.Id);

// ...

public static class EnumerableExtensions
{
    public static IEnumerable<TFirst> Except<TFirst, TSecond, TKey>(
        this IEnumerable<TFirst> first,
        IEnumerable<TSecond> second,
        Func<TFirst, TKey> firstKeySelector,
        Func<TSecond, TKey> secondKeySelector)
    {
        // argument null checking etc omitted for brevity

        var keys = new HashSet<TKey>(second.Select(secondKeySelector));
        return first.Where(x => keys.Add(firstKeySelector(x)));
    }
}

Briefly, make lists to be List<object> and use C# feature from .NET 4.0: dynamic .

Example:

var listDiff = list1
    .AsEnumerable<object>()
    .Except(list2
        .AsEnumerable<object>()
        .Select(row => ((dynamic)row).ID));
public static IEnumerable<TSource> Except<TSource, CSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> TSelector, IEnumerable<CSource> csource, Func<CSource, TKey> CSelector)
    {
        bool EqualFlag = false;
        foreach (var s in source)
        {
            EqualFlag = false;
            foreach (var c in csource)
            {
                var svalue = TSelector(s);
                var cvalue = CSelector(c);
                if (svalue != null)
                {

                    if (svalue.Equals(cvalue))
                    {
                        EqualFlag = true;
                        break;
                    }
                }
                else if (svalue == null && cvalue == null)
                {
                    EqualFlag = true;
                    break;
                }
            }
            if (EqualFlag)
                continue;
            else
            {
                yield return s;
            }
        }

    }

If you just want the Ids in list1 that are not in list2 , you can do:

var idsInList1NotInList2 = list1.Select(user1 => user1.Id)
                                .Except(list2.Select(user2 => user2.Id));

If you need the associated User1 objects too, here's one way (assuming Ids are unique for a User1 object):

// Create lookup from Id to the associated User1 object
var user1sById = list1.ToDictionary(user1 => user1.Id);

// Find Ids from the lookup that are not present for User2s from list2
// and then retrieve their associated User1s from the lookup
var user1sNotInList2 = user1sById.Keys
                                 .Except(list2.Select(user2 => user2.Id))
                                 .Select(key => user1sById[key]);

EDIT: vc74's take on this idea is slightly better; it doesn't require uniqueness.

尝试

list1.Where(user1 => !list2.Any(user2 => user2.Id.Equal(user1.Id)));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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