Operations on Two Collections Using LINQ in C#

Working with two collections in C# is a common scenario, whether you’re joining data, comparing elements, or filtering results. LINQ offers a powerful, readable way to handle these operations efficiently. In this post, we’ll explore how to perform various operations on two collections using LINQ, including how to handle object collections by implementing custom comparisons.

Using Join for Inner Joins

The Join method in LINQ combines two collections based on a matching key, similar to SQL’s inner join. Here’s how you can perform an inner join between two collections, such as Employee and Department, using method syntax:

var employees = new List<Employee> { /* Employee collection */ };
var departments = new List<Department> { /* Department collection */ };

var employeeDepartments = employees.Join(
    departments,
    employee => employee.DepartmentId,
    department => department.Id,
    (employee, department) => new
    {
        EmployeeName = employee.Name,
        DepartmentName = department.Name
    });

foreach (var item in employeeDepartments)
{
    Console.WriteLine($"{item.EmployeeName} works in {item.DepartmentName}");
}

Using GroupJoin for Left Joins

The GroupJoin method provides a way to perform a left join between two collections. This means you get all elements from the first collection, even if there is no matching element in the second collection. Here’s how you can use GroupJoin:

var employeeDepartments = departments.GroupJoin(
    employees,
    department => department.Id,
    employee => employee.DepartmentId,
    (department, empGroup) => new
    {
        DepartmentName = department.Name,
        Employees = empGroup.Select(e => e.Name).ToList()
    });

foreach (var item in employeeDepartments)
{
    Console.WriteLine($"{item.DepartmentName}: {string.Join(", ", item. Employees)}");
}

Finding Common Elements Using Intersect

To find common elements between two collections, LINQ provides the Intersect method. It returns a collection containing elements that are present in both collections.

Example with Primitive Types

Here’s how you can use Intersect to find common elements between two lists of integers:

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

var commonElements = list1.Intersect(list2);

Console.WriteLine("Common elements:");
foreach (var item in commonElements)
{
    Console.WriteLine(item); 
} 

// Output: 3,4,5

When working with collections of objects, Intersect uses reference equality by default. To customize the comparison, you can either implement IEquatable<T> on your object or use a custom IEqualityComparer<T>.

Option 1: Implementing IEquatable<T>

To define equality based on object properties, such as an Id property, you can implement IEquatable<T>:

public class Person : IEquatable<Person>
{
    public int Id { get; set; }
    public string Name { get; set; }

    public bool Equals(Person other)
    {
        if (other == null)
            return false;
        return this.Id == other.Id; // Define equality based on Id
    }

    public override int GetHashCode()
    {
        return this.Id.GetHashCode(); // Must be consistent with Equals
    }
}


Now you can use Intersect on collections of Person:

var list1 = new List<Person>
{
    new Person { Id = 1, Name = "Alice" },
    new Person { Id = 2, Name = "Bob" },
    new Person { Id = 3, Name = "Charlie" }
};

var list2 = new List<Person>
{
    new Person { Id = 2, Name = "Bob" },
    new Person { Id = 3, Name = "Charlie" },
    new Person { Id = 4, Name = "David" }
};

var commonPeople = list1.Intersect(list2);

foreach (var person in commonPeople)
{
    Console.WriteLine($"{person.Name} is common in both collections");
}

// Output
// Bob is common in both collections
// Charlie is common in both collections

Option 2: Using a Custom IEqualityComparer<T>

If you prefer not to modify the class, you can create a custom comparer by implementing IEqualityComparer<T>:

public class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (x == null || y == null)
            return false;
        return x.Id == y.Id; // Define equality based on Id
    }

    public int GetHashCode(Person obj)
    {
        return obj.Id.GetHashCode();
    }
}

Then use the comparer with Intersect:

var commonPeople = list1.Intersect(list2, new PersonComparer());

foreach (var person in commonPeople)
{
    Console.WriteLine($"{person.Name} is common in both collections");
}

Combining Unique Elements with Union

The Union method combines two collections, removing any duplicate elements. This is helpful when you want to merge two collections into one, ensuring there are no duplicates:

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

var unionList = list1.Union(list2);

Console.WriteLine(string.Join(", ", unionList)); // Output: 1, 2, 3, 4, 5

Finding Differences with Except

The Except method returns elements from the first collection that are not present in the second. This can be useful for identifying what is unique to one collection:

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

var exceptList = list1.Except(list2);

Console.WriteLine(string.Join(", ", exceptList)); // Output: 1, 2

Merging Collections Element-by-Element with Zip

Zip takes two collections and combines their elements pair by pair. It’s useful when you want to process two lists together:

var list1 = new List<string> { "Alice", "Bob" };
var list2 = new List<int> { 25, 30 };

var zippedList = list1.Zip(list2, (name, age) => new { Name = name, Age = age });

foreach (var item in zippedList)
{
    Console.WriteLine($"{item.Name} is {item.Age} years old");
}

// Output
// Alice is 25 years old
// Bob is 30 years old

Closing Thoughts

Using LINQ to operate on two collections simplifies many common tasks, such as joining, finding common elements, and merging data. Whether you’re dealing with simple types or complex objects, LINQ provides expressive, readable methods that make working with collections more intuitive.

By using methods like Join, Intersect, and Union, you can easily handle two collections and customize comparisons with custom equality comparers or IEquatable<T> implementations.

These techniques offer a powerful and flexible way to manipulate collections and make your C# code more efficient and concise.

Leave a Comment

Your email address will not be published. Required fields are marked *