187

I Have two generic list filled with CustomsObjects.

I need to retrieve the difference between those two lists(Items who are in the first without the items in the second one) in a third one.

I was thinking using .Except() was a good idea but I don't see how to use this.. Help!

14 Answers14

336

Using Except is exactly the right way to go. If your type overrides Equals and GetHashCode, or you're only interested in reference type equality (i.e. two references are only "equal" if they refer to the exact same object), you can just use:

var list3 = list1.Except(list2).ToList();

If you need to express a custom idea of equality, e.g. by ID, you'll need to implement IEqualityComparer<T>. For example:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

Then use:

var list3 = list1.Except(list2, new IdComparer()).ToList();

Note that this will remove any duplicate elements. If you need duplicates to be preserved, it would probably be easiest to create a set from list2 and use something like:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 27
    Just as an aside, I'd add that `Except` is a *Set* operation, then the resulting list will have distinct values, e.g `{'A','A','B','C'}.Except({'B','C'})` returns `{'A'}` – digEmAll Apr 12 '11 at 14:10
  • 6
    I have this: {'A','A','B','C'}.Except({'B','C'}) returns {'A'} which works... however {'B','C'}.Except({'A','B','C'}) DOES NOT return {'A'} – MrLister Oct 21 '16 at 17:41
  • 4
    The set difference of two sets is defined as the members of the first set that do not appear in the second set (quoting MS). So, {B,C}.Except({A,B,C}) should return nothing as B and C are both in the second set. Not a bug, more to do with the mathematical definition of the function. – Steve Hibbert Apr 18 '17 at 13:17
  • So @JonSkeet if i want to compare two lists on the basis of let's say 2 properties i can write this way comparer and get the items that are not equal right? – Ehsan Sajjad Jul 31 '18 at 19:42
  • @EhsanSajjad: Well you'd get the items from list A that aren't in list B or vice versa. – Jon Skeet Aug 01 '18 at 05:58
  • Years later, I would point out that if `list2` has duplicates converting it to a set doesn't work properly, if what you want is the _difference_ between lists. – NetMage Jul 10 '19 at 22:02
  • 1
    @NetMage: The OP stated they want "Items who are in the first without the items in the second one" - that sounds like a set difference to me. If the first list contains { 5, 5, 5, 5, 1 } and the second list contains { 5 } then only 1 is in the first list but not the second. – Jon Skeet Jul 11 '19 at 06:38
92

You could do something like this:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));
Ben
  • 10,056
  • 5
  • 41
  • 42
Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • 1
    it saved me one foreach loop. – alice7 Apr 02 '12 at 23:27
  • 12
    Shouldn't one of those "l.someproperty" be "p.someproperty"? – Manos Dilaverakis Jan 15 '13 at 21:59
  • 6
    This can be simplified with customlist.Where(p => otherlist.Any(l => p.someproperty != l.someproperty)); – Dhanuka777 Nov 23 '16 at 03:45
  • 4
    @Dhanuka777 that's incorrect. The `Any` should be an `All` if you want to do it like that. This is because the second list could have the item from the first list, but if it's not the first one checked then it will immediately get a true for `p.someproperty != l.someproperty`. This results in items being returned that exist in both lists. Shame on the 6 people who upvoted this. – Murphybro2 Jul 05 '19 at 13:55
71

I think important to emphasize - using Except method will return you items who are in the first without the items in the second one only. It does not return those elements in second that do not appear in first.

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

But if you want get real difference between two lists:

Items who are in the first without the items in the second one and items who are in the second without the items in the first one.

You need using Except twice:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

Or you can use SymmetricExceptWith method of HashSet. But it changes the set on which called:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7
Stas Boyarincev
  • 3,690
  • 23
  • 23
  • That's helpful. Just be aware the OP made it clear they did not want full difference: "I need to retrieve the difference between those two lists (*Items who are in the first without the items in the second one*)". – rsenna Apr 15 '19 at 15:52
13
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

Note: list3 will contain the items or objects that are not in both lists. Note: Its ToList() not toList()

Daniel Nicolay
  • 153
  • 1
  • 7
11
var third = first.Except(second);

(you can also call ToList() after Except(), if you don't like referencing lazy collections.)

The Except() method compares the values using the default comparer, if the values being compared are of base data types, such as int, string, decimal etc.

Otherwise the comparison will be made by object address, which is probably not what you want... In that case, make your custom objects implement IComparable (or implement a custom IEqualityComparer and pass it to the Except() method).

rsenna
  • 11,775
  • 1
  • 54
  • 60
5

Following helper can be usefull if for such task:

There are 2 collections local collection called oldValues and remote called newValues From time to time you get notification bout some elements on remote collection have changed and you want to know which elements were added, removed and updated. Remote collection always returns ALL elements that it has.

    public class ChangesTracker<T1, T2>
{
    private readonly IEnumerable<T1> oldValues;
    private readonly IEnumerable<T2> newValues;
    private readonly Func<T1, T2, bool> areEqual;

    public ChangesTracker(IEnumerable<T1> oldValues, IEnumerable<T2> newValues, Func<T1, T2, bool> areEqual)
    {
        this.oldValues = oldValues;
        this.newValues = newValues;
        this.areEqual = areEqual;
    }

    public IEnumerable<T2> AddedItems
    {
        get => newValues.Where(n => oldValues.All(o => !areEqual(o, n)));
    }

    public IEnumerable<T1> RemovedItems
    {
        get => oldValues.Where(n => newValues.All(o => !areEqual(n, o)));
    }

    public IEnumerable<T1> UpdatedItems
    {
        get => oldValues.Where(n => newValues.Any(o => areEqual(n, o)));
    }
}

Usage

        [Test]
    public void AddRemoveAndUpdate()
    {
        // Arrange
        var listA = ChangesTrackerMockups.GetAList(10); // ids 1-10
        var listB = ChangesTrackerMockups.GetBList(11)  // ids 1-11
            .Where(b => b.Iddd != 7); // Exclude element means it will be delete
        var changesTracker = new ChangesTracker<A, B>(listA, listB, AreEqual);

        // Assert
        Assert.AreEqual(1, changesTracker.AddedItems.Count()); // b.id = 11
        Assert.AreEqual(1, changesTracker.RemovedItems.Count()); // b.id = 7
        Assert.AreEqual(9, changesTracker.UpdatedItems.Count()); // all a.id == b.iddd
    }

    private bool AreEqual(A a, B b)
    {
        if (a == null && b == null)
            return true;
        if (a == null || b == null)
            return false;
        return a.Id == b.Iddd;
    }
user3526723
  • 101
  • 3
  • 2
3

To get unique differences from both lists you could merge them (Union) Except those values which are same inside both lists (Intersect) e.g.:

var list1 = new List<int> { 1, 2, 3, 4, 5 };
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var diffs = list1.Union(list2).Except(list1.Intersect(list2));

For complex types implement IComparable if you comparing your instances all the time with the same compare pattern. If you need different compare pattern in some cases than you can always create an class which implements IEqualityComparer.

croban
  • 387
  • 2
  • 7
1

Since the Except extension method operates on two IEumerables, it seems to me that it will be a O(n^2) operation. If performance is an issue (if say your lists are large), I'd suggest creating a HashSet from list1 and use HashSet's ExceptWith method.

foson
  • 10,037
  • 2
  • 35
  • 53
  • 9
    `Enumerable.Except` internally uses `HashSet` or something similar. It definitely does not use the naive O(n^2) algorithm. – Jim Mischel Apr 12 '11 at 15:01
  • @Jim Mischel: You are right, Enumerable.Except uses an internal Set data structure and adds items from both IEnumerables into the Set. Wish the documentation would say something about that. – foson Apr 12 '11 at 23:43
1

here is my solution:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}
Roni Al Ka
  • 26
  • 3
-1

bit late but here is working solution for me

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

In above mention code I am getting the properties difference between my base class and derived class list

Atul Chaudhary
  • 3,698
  • 1
  • 31
  • 51
-1
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();
Teezy7
  • 515
  • 5
  • 9
-2
List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
-3
        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();
        List<int> listDifference = new List<int>();

        foreach (var item1 in list1)
        {
            foreach (var item2 in list2)
            {
                if (item1 != item2)
                    listDifference.Add(item1);
            }
        }
Milad
  • 31
  • 4
-3

If both your lists implement IEnumerable interface you can achieve this using LINQ.

list3 = list1.where(i => !list2.contains(i));
dcarneiro
  • 7,060
  • 11
  • 51
  • 74