When working with collections in C#, we sometimes need to manipulate them by performing operations such as comparisons, mergers, or finding set differences between them. The LINQ Except method comes in handy when we want to find the set difference between two sequences, as it returns a new sequence containing elements in the first sequence that are not in the second sequence. In this article, we will learn how the Except method works in LINQ and how to apply it in real-world scenarios. 

To download the source code for this article, you can visit our GitHub repository.

Let’s understand how the LINQ Except method works in C# without further ado!

Extract Differences With the LINQ Except Method

We are going to use a complex type, such as an Employee class, for our examples:

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
List<Employee> employees = new() 
{
    new Employee {ID = 1, Name = "John Doe", Age = 28, Department = "Sales"},
    new Employee {ID = 2, Name = "Emily Sanders", Age = 29, Department = "Sales"},
    new Employee {ID = 3, Name = "Benjamin Winters", Age = 30, Department = "Sales"},
    new Employee {ID = 4, Name = "Sheila Foxx", Age = 31, Department = "Marketing"},
    new Employee {ID = 5, Name = "Alexander Flemming", Age = 31, Department = "Marketing"},
    new Employee {ID = 6, Name = "Grace Jones", Age = 32, Department = "IT"},
    new Employee {ID = 7, Name = "Ava Knowles", Age = 32, Department = "IT"},
    new Employee {ID = 8, Name = "Isabela Wambui", Age = 48, Department = "Marketing"},
    new Employee {ID = 9, Name = "David Otieno", Age = 60, Department = "HR"},
    new Employee {ID = 10, Name = "Emma Charlotte", Age = 33, Department = "HR"},
    new Employee {ID = 11, Name = "Michael Bay", Age = 34, Department = "HR"},
    new Employee {ID = 12, Name = "Mary Jane", Age = 35, Department = "Sales"}
};

Let’s assume we want to get a list of employees not in sales using Except():

var empNotInSales = employees.Except(employees.Where(e => e.Department.Equals("Sales"))).ToList();

return empNotInSales;

First, we find the set difference by returning elements from Employees that are not in the sales department, which helps us achieve our goal. Note that when one of the sequences we are comparing is null, a System.ArgumentNullException will be thrown. 

In this example, we made use of one of the overloads, Except<TSource>(IQueryable<TSource>, IEnumerable<TSource>). Next, let’s implement an overload that adds an IEqualityComparer object. 

How to Use IEqualityComparer Comparer With the LINQ Except Method in C#

Sometimes, we may want to perform a set difference between two string sequences with different casing.

Let’s assume we have a list of employees in the IT department with employee names in lowercase:

List<Employee> employeesIT = new()
{
    new Employee {ID = 6, Name = "grace jones", Age = 32, Department = "IT"},
    new Employee {ID = 7, Name = "ava knowles", Age = 32, Department = "IT"}
};

The default comparator object is case sensitive; therefore, when we want to retrieve a list of employees not in the IT department, Ava and Grace will still be returned if we use it. 

Let’s implement an example with our list of employees to perform the set difference of their names while ignoring their case sensitivity:

var employeesNotInIT = employees.Select(e => e.Name)
     .Except(employeesIT.Select(s => s.Name), StringComparer.OrdinalIgnoreCase).ToList();

return employeesNotInIT;

Here, we pass StringComparer.OrdinalIgnoreCase as an argument to return the list of employees not in the IT department, regardless of their case. StringComparer already implements the IEqualityComparer interface, which helps us control how we compare string values. 

Next, let’s learn how to implement the IEqualityComparer interface using our Employee class example. 

Custom IEqualityComparer Implementation

In some cases, we may wish to have more granular control over performing set differences between two sequences, especially for complex types. Let’s implement the interface using our Employee class example to understand how the interface works:

public class EmployeeComparer : IEqualityComparer<Employee>
{
    public bool Equals(Employee? x, Employee? y)
    {
        if (x is null && y is null) return true;
        if (x is null || y is null) return false;

        return x.ID == y.ID && x.Name == y.Name && x.Age == y.Age && x.Department == y.Department;
    }

    public int GetHashCode([DisallowNull] Employee obj)
    {
        var hashID = obj.ID.GetHashCode();
        var hashName = obj.Name.GetHashCode();

        return hashID ^ hashName;
    }
}

The Equals() method checks whether two Employee objects are equal. First, we check whether both objects have the same object reference and return true. Next, we check if any of the objects have null object references and return false. Finally, we compare each of the properties in our Employee class for equality. 

On the other hand, the GetHashCode() method implementation calculates the hash codes for the ID and Name properties of the Employee object. Afterward, we perform an XOR operation to get a combined hash value, which can be useful when we want to use it as a key in hash-based collections such as dictionaries

We can now achieve the same objective as our previous example:

EmployeeComparer comparer = new ();

var employeesInSales = employees.Where(e => e.Department.Equals("Sales")).ToList();

return employees.Except(employeesInSales, comparer).ToList();

This time, when we call the Except() method, our custom implementation of the Equals() method will be used.

When to Use LINQ Except Method

We can use Except() when we want to retrieve unique sequences of elements, as it returns a new sequence after performing the set difference between two sequences. 

Beyond that, we can use Except() when we want to track changes between two sequences. For example, when we add a new Employee record, we can track changes between two lists, forming the basis for tasks such as incremental backups and audit trails. 

Finally, note that Except() does not make any changes to the original sequences but returns a new instance containing distinct elements. Also, the operation retains the same order of elements as in the first sequence. 

Conclusion

In this article, we learned how to use the Except method in LINQ. We can leverage this method to find the set difference of both simple and complex types. When we need greater control over comparing elements, we can leverage the IEqualityComparer interface. 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!