Using Linq Except not Working as I Thought
Asked Answered
M

5

20

List1 contains items { A, B } and List2 contains items { A, B, C }.

What I need is to be returned { C } when I use Except Linq extension. Instead I get returned { A, B } and if I flip the lists around in my expression the result is { A, B, C }.

Am I misunderstanding the point of Except? Is there another extension I am not seeing to use?

I have looked through and tried a number of different posts on this matter with no success thus far.

var except = List1.Except(List2); //This is the line I have thus far

EDIT: Yes I was comparing simple objects. I have never used IEqualityComparer, it was interesting to learn about.

Thanks all for the help. The problem was not implementing the comparer. The linked blog post and example below where helpful.

Mernamero answered 29/5, 2013 at 22:10 Comment(3)
What's in those lists exactly?Litta
What is the data type of your items. Is it a class? This link might help you #1646391Skit
They are simple objects with a couple of properties for the moment. I'll look through your link.Mernamero
T
21

If you are storing reference types in your list, you have to make sure there is a way to compare the objects for equality. Otherwise they will be checked by comparing if they refer to same address.

You can implement IEqualityComparer<T> and send it as a parameter to Except() function. Here's a blog post you may find helpful.

edit: the original blog post link was broken and has been replaced above

Topographer answered 29/5, 2013 at 22:46 Comment(3)
To be clear this is the solution that solved my problem long ago and is a viable option, depending on your scenario.Mernamero
I have a List<MyType>, and MyType implements IEquatable<T>. When I use the Except method, however, I still get what the OP refers to as { A, B, C }. What is the problem?Siana
It's such a shame that article isn't formatted very nicely.Randi
T
9

So just for completeness...

// Except gives you the items in the first set but not the second
    var InList1ButNotList2 = List1.Except(List2);
    var InList2ButNotList1 = List2.Except(List1);
// Intersect gives you the items that are common to both lists    
    var InBothLists = List1.Intersect(List2);

Edit: Since your lists contain objects you need to pass in an IEqualityComparer for your class... Here is what your except will look like with a sample IEqualityComparer based on made up objects... :)

// Except gives you the items in the first set but not the second
        var equalityComparer = new MyClassEqualityComparer();
        var InList1ButNotList2 = List1.Except(List2, equalityComparer);
        var InList2ButNotList1 = List2.Except(List1, equalityComparer);
// Intersect gives you the items that are common to both lists    
        var InBothLists = List1.Intersect(List2);

public class MyClass
{
    public int i;
    public int j;
}

class MyClassEqualityComparer : IEqualityComparer<MyClass>
{
    public bool Equals(MyClass x, MyClass y)
    {
        return x.i == y.i &&
               x.j == y.j;
    }

    public int GetHashCode(MyClass obj)
    {
        unchecked
        {
            if (obj == null)
                return 0;
            int hashCode = obj.i.GetHashCode();
            hashCode = (hashCode * 397) ^ obj.i.GetHashCode();
            return hashCode;
        }
    }
}
Telegenic answered 29/5, 2013 at 22:36 Comment(2)
InList1ButNotList2 gives be { C, B, A }, which is what has been happening.Mernamero
@Schanckopotomus Ahh... your lists are objects and their properties are equal but the objects are not reference equal right?Telegenic
G
8

You simply confused the order of arguments. I can see where this confusion arose, because the official documentation isn't as helpful as it could be:

Produces the set difference of two sequences by using the default equality comparer to compare values.

Unless you're versed in set theory, it may not be clear what a set difference actually is—it's not simply what's different between the sets. In reality, Except returns the list of elements in the first set that are not in the second set.

Try this:

var except = List2.Except(List1); // { C }
Gilliette answered 29/5, 2013 at 22:15 Comment(1)
If change the list order I get { A, B, C }Mernamero
B
0

Writing a custom comparer does seem to solve the problem, but I think https://mcmap.net/q/662893/-list-except-is-not-working is a much more simple and elegant solution.

It overwrites the GetHashCode() and Equals() methods in your object defining class, then the default comparer does its magic without extra code cluttering up the place.

Bandage answered 11/7, 2020 at 20:3 Comment(0)
S
-1

Just for Ref: I wanted to compare USB Drives connected and available to the system.

So this is the class which implements interface IEqualityComparer

public class DriveInfoEqualityComparer : IEqualityComparer<DriveInfo>
{
    public bool Equals(DriveInfo x, DriveInfo y)
    {
        if (object.ReferenceEquals(x, y))
            return true;
        if (x == null || y == null)
            return false;
        // compare with Drive Level
        return x.VolumeLabel.Equals(y.VolumeLabel);
    }

    public int GetHashCode(DriveInfo obj)
    {
        return obj.VolumeLabel.GetHashCode();
    }
}

and you can use it like this

var newDeviceLst = DriveInfo.GetDrives()
                            .ToList()
                            .Except(inMemoryDrives, new DriveInfoEqualityComparer())
                            .ToList();
Simsar answered 21/9, 2018 at 11:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.