What is LINQ?

LINQ is ETL for the data in your program. Start from any collection, chain methods such as Where, OrderBy and Select that each transform the items, and finish with ToList, Count or First. A query runs when its results are used.

Journeyman · C# · Updated

After this lesson you can

  • Chain LINQ methods from a source, through transformations, to a result such as ToList or Count
  • Explain that a LINQ query runs when its results are used, not when it's written, and use ToList to keep the results

Key takeaways

  • A LINQ chain starts from any collection, transforms it with methods such as Where, OrderBy and Select, and ends with a result such as ToList() or Count().
  • LINQ never changes the original collection; it always builds something new from it.
  • A LINQ query runs when its results are used, such as by a foreach or Count(), not when it's written. This is deferred execution.
  • End a chain with ToList() to keep the results as they are now, so they won't change behind your back or run the query again.

LINQ (Language-Integrated Query, said "link") is a set of methods for working with collections: filtering, sorting, reshaping, totaling. It's ETL for the data inside your program:

ETL In LINQ Example
Extract from a source Any collection: a list, an array, a set, the lines of a file names
Transform, one step at a time Methods that each pass on a changed stream of items .Where(...), .OrderBy(...), .Select(...)
Load into a destination A method that turns the stream back into data at rest .ToList(), .Count(), .First()

From a loop to LINQ

Here's a job you can already do with a loop: the names longer than 4 letters, in alphabetical order, in capitals.

List<string> names = new List<string> { "Olivia", "Ethan", "Sophia", "Liam", "Emma", "Noah", "Ava", "Jackson" };

List<string> result = new List<string>();
foreach (string name in names)
{
    if (name.Length > 4)
    {
        result.Add(name.ToUpper());
    }
}
result.Sort();

Console.WriteLine(string.Join(", ", result));
ETHAN, JACKSON, OLIVIA, SOPHIA

And here's the same job with LINQ:

List<string> names = new List<string> { "Olivia", "Ethan", "Sophia", "Liam", "Emma", "Noah", "Ava", "Jackson" };

List<string> result = names
    .Where(name => name.Length > 4)
    .OrderBy(name => name)
    .Select(name => name.ToUpper())
    .ToList();

Console.WriteLine(string.Join(", ", result));
ETHAN, JACKSON, OLIVIA, SOPHIA

Read it top to bottom, like a recipe: take the names, keep the ones longer than 4, put them in order, make each one capitals, and collect them into a list. Each line is one step, and each step gets a lambda saying what to do with one item.

The loop says how to do it: make a list, loop, check, add, sort. LINQ says what you want, and reads almost like the sentence describing it. (It's the same thinking as SQL: WHERE, ORDER BY and SELECT are there for a reason.)

The steps

  • The source can be any collection, because LINQ's methods work on IEnumerable<T>: the "you can go through my items one by one" contract from Software Interfaces. Lists, arrays, sets, dictionaries and File.ReadLines all keep it. LINQ's methods come with using System.Linq;, which every project already has built in.
  • The transformations each take a stream of items and hand on a new one. Where hands on fewer items, OrderBy the same items in a new order, Select a changed item for each one.
  • The destination ends the chain: ToList() collects the items into a List<T>, ToArray() into an array, and methods such as Count(), Sum() and First() reduce them to one value.

The original list is never changed. LINQ always builds something new from it.

A query runs when it's used

Here's something surprising. Without the last step, a LINQ chain doesn't do anything yet. It's a recipe, not a meal: it runs when something asks for its items, such as a foreach, ToList() or Count().

List<int> scores = new List<int> { 72, 95, 48 };

IEnumerable<int> passes = scores.Where(score => score >= 60);

scores.Add(88);

Console.WriteLine(passes.Count());
3

passes was written when the list had two passing scores, but it counts 3, because the query only ran at passes.Count(), after 88 was added. This is called deferred execution: the work is put off until the results are needed.

When you want the results now, frozen as they are, finish with ToList():

List<int> scores = new List<int> { 72, 95, 48 };

List<int> passes = scores.Where(score => score >= 60).ToList();

scores.Add(88);

Console.WriteLine(passes.Count);
2

A good habit: end a chain with ToList() (or Count(), First(), …) unless you have a reason not to. Then the result is data at rest, and it won't change behind your back or run the query again every time you loop over it.

Check yourself

2 questions to try on the lesson page.

So what

LINQ turns "make a list, loop, check, add" into one readable sentence: source, transformations, destination. It's how most C# code works with collections, and the same methods will query a database through Entity Framework Core later in this module.

Key terms

Cheat sheets