Common LINQ Operations

Filter collections and sort the output using LINQ queries.

Basic syntax

All LINQ queries have nearly identical structures:

var result = from item in collection
             select item;
  • The collection is a collection object (like an array or list).
  • The item refers to an individual element of that collection.
  • The select statement is used to choose what should be returned as a result.

This is the basic syntax. It can be extended from here through filtering, sorting, projection, and so on. We’ll explore these operations individually.

Filter

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

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqQueries
{
class Program
{
static void Main(string[] args)
{
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 // Where there is no remainder when dividing by 2
select number;
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
}
}
}

Order the output

We use the orderby and orderby descending clauses to sort the resulting collection:

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqQueries
{
class Program
{
static void Main(string[] args)
{
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 // Notice this keyword
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 + " ");
}
}
}
}

Projection

So far, we’ve been selecting whole items from the collection. In the previous lesson, we fetched Employee objects from the List<Employee> collection. Projection is customized selection. Instead of returning whole objects, we can choose what properties we want to select and encapsulate them inside either a different class or an anonymous type.

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqQueries
{
class Program
{
static void Main(string[] args)
{
// Our list of employees
var employees = new List<Employee>()
{
new Employee() { FullName = "John Doe", Age = 32, Salary = 170000m },
new Employee() { FullName = "Chris Patrick", Age = 24, Salary = 156000m },
new Employee() { FullName = "Amanda Watson", Age = 25, Salary = 165000m },
new Employee() { FullName = "Adam Smith", Age = 24, Salary = 120000m },
new Employee() { FullName = "Kirk Marks", Age = 29, Salary = 210000m },
new Employee() { FullName = "Bree Lang", Age = 25, Salary = 320000m }
};
var salariesAndAges = from employee in employees
select new
{
employee.Salary,
employee.Age
}; // Here, we encapsulated the selection in an anonymous type
// But we could also create an instance of a named type
foreach (var item in salariesAndAges)
{
Console.WriteLine(item);
}
}
}
}

The let operator

We can also use the let keyword to perform some operations and store the results in temporary variables:

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqQueries
{
class Program
{
static void Main(string[] args)
{
// Our list of employees
var employees = new List<Employee>()
{
new Employee() { FullName = "John Doe", Age = 32, Salary = 170000m },
new Employee() { FullName = "Chris Patrick", Age = 24, Salary = 156000m },
new Employee() { FullName = "Amanda Watson", Age = 25, Salary = 165000m },
new Employee() { FullName = "Adam Smith", Age = 24, Salary = 120000m },
new Employee() { FullName = "Kirk Marks", Age = 29, Salary = 210000m },
new Employee() { FullName = "Bree Lang", Age = 25, Salary = 320000m }
};
var employeesNewSalary = from employee in employees
// Here we are performing a calculation
// and storing the result in a temp variable
let increasedSalary = employee.Salary * 1.15m
// Projection using a named type
select new Employee()
{
FullName = employee.FullName,
Age = employee.Age,
Salary = increasedSalary // using our variable
};
foreach (var employee in employeesNewSalary)
{
Console.WriteLine($"{employee.FullName}: {employee.Salary}.");
}
}
}
}

Multiple sources

It’s also possible to query several collections simultaneously. This can yield all possible combinations:

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqQueries
{
class Program
{
static void Main(string[] args)
{
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 // querying both collections
select $"{firstName} {lastName}"; // we can project however we want
foreach (var possibleCombination in possibleCombinations)
{
Console.WriteLine(possibleCombination);
}
}
}
}

Method syntax

Using the query syntax isn’t the only way to harness the power of LINQ. We can achieve the same results using the 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 to the Where() method:

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqQueries
{
class Program
{
static void Main(string[] args)
{
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);
}
}
}
}

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 an anonymous method that takes the number parameter (an individual element of the numbers collection) and returns an indicator of whether it’s divisible by two without any remainder.

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