Common LINQ Operations

Learn to perform filtering, sorting, projection, and other common operations using LINQ query and method syntax.

Most LINQ queries written using query syntax follow a predictable structure:

var result = from item in collection
select item;
The foundational structure of a LINQ query
  • Line 1: We define the data source (collection) and a range variable (item) that represents each individual element during iteration.

  • Line 2: We use the select statement to determine exactly what the query returns.

This basic syntax can be extended with filtering, sorting, projection, and other operations, which we explore individually.

Filter

To select items that match specific criteria, we use the where clause. After the where keyword, we provide a boolean expression that determines how the filtering occurs.

C# 14.0
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int>()
{
1, 2, 3, 75, 43, 12, 99, 22, 76, 12, 874, 23
};
var evenNumbers = from number in numbers
where number % 2 == 0
select number;
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
  • Line 1: We import the System.Collections.Generic namespace to access the List<T> collection type.

  • Lines 4–7: We initialize a list of integers to serve as our data source.

  • Line 10: We apply the where clause with a condition that checks if the number is divisible by 2 with no remainder.

  • Lines 13–16: We loop through the filtered sequence and print each even number to the console.

Order the output

We use the orderby and orderby descending clauses to sort the resulting collection. Here is how we apply them to our list of numbers.

C# 14.0
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int>()
{
1, 2, 3, 75, 43, 12, 99, 22, 76, 12, 874, 23
};
// From smallest to largest
var evenNumbersAscending = from number in numbers
where number % 2 == 0
orderby number
select number;
// From largest to smallest
var evenNumbersDescending = from number in numbers
where number % 2 == 0
orderby number descending
select number;
Console.Write("In ascending order:\t");
foreach (var number in evenNumbersAscending)
{
Console.Write(number + " ");
}
Console.Write("\nIn descending order:\t");
foreach (var number in evenNumbersDescending)
{
Console.Write(number + " ");
}
  • Line 12: We use the orderby keyword to sort the results from smallest to largest, which is the default behavior.

  • Line 18: We append the descending keyword to the orderby clause to reverse the sort direction.

Projection

So far, we have been selecting whole items from the collection. Projection enables customized selection. Instead of returning complete objects, we can choose specific properties and encapsulate them inside either a different class or an anonymous type.

First, we define our custom type using a file-scoped namespace.

C# 14.0
namespace LinqQueries;
public class Employee
{
public string FullName { get; set; } = string.Empty;
public int Age { get; set; }
public decimal Salary { get; set; }
}
  • Line 5: We initialize the FullName property with string.Empty to satisfy non-nullable reference type requirements and prevent compiler warnings.

With our data model ready, we can project specific properties into anonymous types within our main program logic.

C# 14.0
namespace LinqQueries;
public class Employee
{
public string FullName { get; set; } = string.Empty;
public int Age { get; set; }
public decimal Salary { get; set; }
}
  • Line 5: We import the LinqQueries namespace to access our custom Employee model.

  • Lines 18–21: We use the select new syntax to create an anonymous type containing only the Salary and Age properties, discarding the rest of the employee data.

The let operator

We can also use the let keyword to perform operations and store the results in temporary variables inside the query itself.

To demonstrate this, we rely on the exact same Employee class defined in the previous section. Now we write a query that calculates a new salary for each employee and stores it in a temporary variable before projecting the final result.

C# 14.0
namespace LinqQueries;
public class Employee
{
public string FullName { get; set; } = string.Empty;
public int Age { get; set; }
public decimal Salary { get; set; }
}
  • Line 16: We use the let keyword to calculate a 15% salary increase and store it in the increasedSalary variable.

  • Lines 18–22: We project the result into a new, strongly typed Employee object, passing our increasedSalary variable into the Salary property.

Multiple sources

It is also possible to query several collections simultaneously. This approach yields all possible combinations of the elements from the specified collections, functioning similarly to a cross join in SQL.

We define two separate lists of strings and combine them in a single query.

C# 14.0
using System.Collections.Generic;
using System.Linq;
var firstNames = new List<string>()
{
"John", "Patrick", "Lynda", "Albert", "Lionel", "Amanda"
};
var lastNames = new List<string>()
{
"Davids", "Brooke", "Chappell", "Links", "Johnson"
};
var possibleCombinations = from firstName in firstNames
from lastName in lastNames
select $"{firstName} {lastName}";
foreach (var possibleCombination in possibleCombinations)
{
Console.WriteLine(possibleCombination);
}
  • Lines 15–16: We use two consecutive from clauses to query both the firstNames and lastNames collections together.

  • Line 17: We use string interpolation within the select statement to combine the items into a single string.

Method syntax

Query syntax is not the only way to use LINQ. We can achieve the exact same results using extension methods, which we can call on any object that implements the IEnumerable interface.

These extension methods become available as soon as we connect to the System.Linq namespace. For instance, the where clause of the query syntax translates directly to the Where() method. We can apply this to our original list of numbers.

C# 14.0
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int>()
{
1, 2, 3, 75, 43, 12, 99, 22, 76, 12, 874, 23
};
var evenNumbers = numbers.Where(number => number % 2 == 0);
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
  • Line 9: We call the Where() extension method directly on the numbers list.

The Where() method accepts a delegate that takes an individual element of the collection and returns a bool value. In the code above, we supply a lambda expression that takes the number parameter and returns a boolean indicating whether it is divisible by two without a remainder.

Similar to the Where() method, there are OrderBy() and OrderByDescending() methods, among others.