Sewing threads

Three set-like LINQ methods: Intersect, Union, and Except

So far we have covered some of the most common LINQ methods. This time let’s cover three LINQ methods that work like set operations: Intersect, Union, and Except.

Like the Aggregate method, we don’t use these methods every day, but they will come in handy from time to time.

1. Intersect

Intersect() finds the common elements between two collections.

Let’s find the movies we both have watched and rated in our catalogs.

var mine = new List<Movie>
{
    // We have not exactly a tie here...
    new Movie("Terminator 2", 1991, 4.7f),
    //        ^^^^^^^^^^^^^^
    new Movie("Titanic", 1998, 4.5f),
    new Movie("The Fifth Element", 1997, 4.6f),
    new Movie("My Neighbor Totoro", 1988, 5f)
    //        ^^^^^^^^^^^^^^^^^^^^
};

var yours = new List<Movie>
{
    new Movie("My Neighbor Totoro", 1988, 5f),
    //        ^^^^^^^^^^^^^^^^^^^^
    new Movie("Pulp Fiction", 1994, 4.3f),
    new Movie("Forrest Gump", 1994, 4.3f),
    // We have not exactly a tie here...
    new Movie("Terminator 2", 1991, 5f)
    //        ^^^^^^^^^^^^^^
};

var weBothHaveSeen = mine.Intersect(yours);
Console.WriteLine("We both have seen:");
PrintMovies(weBothHaveSeen);

// Output:
// We both have seen:
// My Neighbor Totoro

static void PrintMovies(IEnumerable<Movie> movies)
{
    Console.WriteLine(string.Join(",", movies.Select(movie => movie.Name)));
}

record Movie(string Name, int ReleaseYear, float Rating);

This time, we have two lists of movies, mine and yours, with the ones I’ve watched and the ones you have watched, respectively. Also, we both have watched “My Neighbor Totoro” and “Terminator 2.”

To find the movies we both have seen (the intersection between our two catalogs), we used Intersect().

But, our example only shows “My Neighbor Totoro.” What happened here?

If we pay close attention, we both have watched “Terminator 2,” but we gave it different ratings. Since we’re using records from C# 9.0, records have member-wise comparison. Therefore, our two “Terminator 2” instances aren’t exactly the same, even though they have the same name. That’s why Intersect() doesn’t return it.

To find the common movies using only the movie name, we can:

Let’s use the IntersectBy() method.

var weBothHaveSeen = mine.IntersectBy(
        yours.Select(yours => yours.Name),
        //    ^^^^^^
        // Your movie names
        (movie) => movie.Name);
        //               ^^^^
        // keySelector: Property to compare by

Console.WriteLine("We both have seen:");
PrintMovies(weBothHaveSeen);

// Output:
// We both have seen:
// Terminator 2,My Neighbor Totoro

Unlike Intersect(), IntersectBy() expects a “keySelector,” a delegate with the property to use as the comparing key, and a second collection with the same type as the keySelector.

Colorful apartments in a building
Photo by Martin Woortman on Unsplash

2. Union

Union() finds the elements from both collections without duplicates.

Let’s find all the movies we have in our catalogs.

var mine = new List<Movie>
{
    new Movie("Terminator 2", 1991, 5f),
    //        ^^^^^^^^^^^^^^
    new Movie("Titanic", 1998, 4.5f),
    new Movie("The Fifth Element", 1997, 4.6f),
    new Movie("My Neighbor Totoro", 1988, 5f)
    //        ^^^^^^^^^^^^^^^^^^^^
};

var yours = new List<Movie>
{
    new Movie("My Neighbor Totoro", 1988, 5f),
    //        ^^^^^^^^^^^^^^^^^^^^
    new Movie("Pulp Fiction", 1994, 4.3f),
    new Movie("Forrest Gump", 1994, 4.3f),
    new Movie("Terminator 2", 1991, 5f)
    //        ^^^^^^^^^^^^^^
};

var allTheMoviesWeHaveSeen = mine.Union(yours);
Console.WriteLine("All the movies we have seen:");
PrintMovies(allTheMoviesWeHaveSeen);

// Output:
// All the movies we have seen:
// Terminator 2,Titanic,The Fifth Element,My Neighbor Totoro,Pulp Fiction,Forrest Gump

static void PrintMovies(IEnumerable<Movie> movies)
{
    Console.WriteLine(string.Join(",", movies.Select(movie => movie.Name)));
}

record Movie(string Name, int ReleaseYear, float Rating);

This time we gave the same rating to our shared movies: “Terminator 2” and “My Neighbor Totoro.” And, Union() showed all the movies from both collections, showing duplicates only once.

Union() works the same way as the union operation in our Math classes.

LINQ has a similar method to “combine” two collections into a single one: Concat(). But, unlike Union(), Concat() returns all elements from both collections without removing the duplicated ones.

.NET 6.0 also has a UnionBy() method to “union” two collections with a keySelector. And, unlike IntersectBy(), we don’t need the second collection to have the same type as the keySelector.

3. Except

Except() finds the elements in one collection that are not present in another one.

This time, let’s find the movies only I have watched.

var mine = new List<Movie>
{
    new Movie("Terminator 2", 1991, 5f),
    new Movie("Titanic", 1998, 4.5f),
    //         ^^^^^^^
    new Movie("The Fifth Element", 1997, 4.6f),
    //         ^^^^^^^^^^^^^^^^^
    new Movie("My Neighbor Totoro", 1988, 5f)
};

var yours = new List<Movie>
{
    new Movie("My Neighbor Totoro", 1988, 5f),
    new Movie("Pulp Fiction", 1994, 4.3f),
    new Movie("Forrest Gump", 1994, 4.3f),
    new Movie("Terminator 2", 1991, 5f)
};

var onlyIHaveSeen = mine.Except(yours);
Console.WriteLine();
Console.WriteLine("Only I have seen:");
PrintMovies(onlyIHaveSeen);

// Output:
// Only I have seen:
// Titanic,The Fifth Element

static void PrintMovies(IEnumerable<Movie> movies)
{
    Console.WriteLine(string.Join(",", movies.Select(movie => movie.Name)));
}

record Movie(string Name, int ReleaseYear, float Rating);

With Except(), we found the movies in mine that are not in yours.

When working with Except(), we should pay attention to the order of the collection because this method isn’t commutative. This means, mine.Except(yours) is not the same as yours.Except(mine).

Likewise, we have ExceptBy() that receives a KeySelector and a second collection with the same type as the keySelector type.

Voilà! These are the Intersect(), Union(), and Except() methods. They work like the Math set operations: intersection, union, and symmetrical difference, respectively. Of the three, I’d say Except is the most common method.

If you want to read more about LINQ, check my quick guide to LINQ, five common LINQ mistakes and how to fix them and what’s new in LINQ with .NET6.

Want to write more expressive code for collections? Join my course, Getting Started with LINQ on Udemy! You'll learn from what LINQ is, to refactoring away from conditionals, and to new methods and overloads from recent .NET versions. Everything you need to know to start working productively with LINQ — in less than two hours.

Happy coding!