少強甘
少強甘

Reputation: 49

How to use Except on two different types?

I can use Except() on two same types like:

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

var expectedList = list1.Except(list2);//Result:  { 1 , 2 , 5 }

But how to do with different types? like:

var cats = new List<Cat>();
var dogs = new List<Dog>();

var exceptCats = cats
             .Except(dogs)
             .On(cat.Age == dog.Age && cat.Name == dogs.Name)

Upvotes: 0

Views: 91

Answers (4)

Guru Stron
Guru Stron

Reputation: 143098

You can use the Where approach but for relatively big collections it can result in performance cost. To tackle this problem you can create custom equality comparer for the specific use case. If both types do not have shared type/interface (base class/interface) then it can look like the following:

class CatDogComparer : IEqualityComparer<object>
{
    public bool Equals(object x, object y)
    {
        return (x, y) switch
        {
            (Dog d, Cat c) => Compare(d, c),
            (Cat c, Dog d) => Compare(d, c),
            _ => false
        };

        bool Compare(Dog d, Cat c) => d.Age == c.Age && d.Name == d.Name;
    }

    public int GetHashCode(object obj) => obj switch
    {
        Dog d => HashCode.Combine(d.Age, d.Name),
        Cat c => HashCode.Combine(c.Age, c.Name),
        _ => obj.GetHashCode() // throw?
    };
}

And usage:

var exceptCats = cats
    .Except(dogs, new CatDogComparer())
    .ToList();

This can be improved by introducing some shared type like:

public interface IHaveNameAndAge
{
    int Age { get; set; }
    string Name { get; set; }
}

public class Cat : IHaveNameAndAge {...}
public class Dog : IHaveNameAndAge {...}

Another approach is to use LINQ (which as far as I remember should also be optimized):

var catsToExclude = from c in cats
    join d in dogs on new { c.Age, c.Name } equals new { d.Age, d.Name }
    select c;

var exceptCats = cats
    .Except(catsToExclude)
    .ToList();

Upvotes: 2

Svyatoslav Danyliv
Svyatoslav Danyliv

Reputation: 27436

Most performant is to use GroupJoin

var exceptCats = 
    from cat in cats
    join dog in dogs 
       on new { cat.Age, cat.Name } equals { dog.Age, dog.Name } into gj
    where !gj.Any()
    select cat;

Upvotes: 2

Peter B
Peter B

Reputation: 24280

Let's rephrase: you want all cats for which no dog exists with the same name and age.

By stating it like that, the Linq you need to use comes almost naturally:

var cats = new List<Cat>();
var dogs = new List<Dog>();

var exceptCats = cats.Where(cat => !dogs.Any(dog => cat.Age == dog.Age && cat.Name == dog.Name));

Upvotes: 0

Yong Shun
Yong Shun

Reputation: 51420

Using Except() for excluding the first set with the second set which both are the same type.

You can use .Where() to filter in this case which has the same behavior.

var exceptCats = cats.Where(cat => !dogs.Any(dog => cat.Age == dog.Age && cat.Name == dog.Name))
    .ToList();

Upvotes: 2

Related Questions