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.
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:
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.