What LINQ Is
LINQ is one set of query methods that works on any sequence, written either as chained method calls or as query keywords.
IEnumerable<TResult> query = source.Where(x => condition).Select(x => projection);LINQ stands for Language Integrated Query. It is a set of methods that let you ask questions of a sequence of values. A sequence is anything you can walk through one item at a time: an array, a List<T>, a Dictionary<TKey, TValue>, the rows coming back from a database. The point of LINQ is that the question looks the same whatever holds the data.
Where the methods come from
They live in the System.Linq namespace, on a static class called Enumerable. They are extension methods, which means they are written as if they belonged to the sequence itself. You call numbers.Where(...) even though Where is not part of the array type. A project made with dotnet new console turns on implicit usings, so using System.Linq; is already in effect. In a project with implicit usings switched off you must add that line, or none of the operators will be visible.
Two spellings, one query
Method syntax chains calls: source.Where(...).Select(...). Query syntax uses keywords: from, where, select. The compiler rewrites query syntax into exactly those method calls, so the two are the same thing and run identically. The t => t > 14 part is a lambda: a small unnamed method written in place, with its parameter on the left of the arrow and the value it returns on the right.
Query syntax must end in a select or group clause. Most code uses method syntax, and reaches for query syntax only when a query needs join or several let steps.
Example
int[] temperatures = [12, 19, 7, 23, 15, 31];
// Method syntax: every operator is a method call on the sequence.
// t => t > 14 is a lambda: an unnamed method, parameter on the left of
// the arrow, returned value on the right.
IEnumerable<int> warmMethod = temperatures
.Where(t => t > 14)
.Select(t => t + 1);
// Query syntax: the same two operators written with keywords. The
// compiler turns this into the same Where and Select calls above.
IEnumerable<int> warmQuery =
from t in temperatures
where t > 14
select t + 1;
Console.WriteLine(string.Join(", ", warmMethod));
Console.WriteLine(string.Join(", ", warmQuery));
// SequenceEqual walks both sequences and compares them element by element.
Console.WriteLine(warmMethod.SequenceEqual(warmQuery));
// Output:
// 20, 24, 16, 32
// 20, 24, 16, 32
// TrueWhen to use it
- Pull the active users out of a list you already loaded into memory, without writing a loop.
- Turn a list of parsed CSV rows into a list of the three fields an API response actually needs.
- Read someone else's query written in query syntax and rewrite it as a method chain you can extend.
More examples
A longer query in both spellings
This is the shorthand next to the longhand. The query form reads top to bottom and gets a free name for the uppercased word; the method form has to invent a tuple to carry two values down the chain. Reach for query syntax when a query needs intermediate names, and for method syntax everywhere else. Knowing both matters because you will meet both in other people's code.
string[] words = ["delta", "alphabet", "charlie", "bravo"];
// Query syntax. The let clause names an intermediate value so the rest of
// the query can use it without recomputing it.
IEnumerable<string> fromQuery =
from w in words
let upper = w.ToUpperInvariant()
where upper.Length > 5
orderby upper
select $"{w}={upper}";
// The same query in method syntax. There is no method called "let", so the
// compiler adds a Select that carries both values forward as a tuple.
IEnumerable<string> fromMethod = words
.Select(w => (Original: w, Upper: w.ToUpperInvariant()))
.Where(pair => pair.Upper.Length > 5)
.OrderBy(pair => pair.Upper)
.Select(pair => $"{pair.Original}={pair.Upper}");
Console.WriteLine(string.Join(" ", fromQuery));
Console.WriteLine(string.Join(" ", fromMethod));
// Output:
// alphabet=ALPHABET charlie=CHARLIE
// alphabet=ALPHABET charlie=CHARLIEThe same operators over three different sources
Three different sources, one vocabulary. A List<T>, a Dictionary<TKey, TValue> and a generated range all satisfy IEnumerable<T>, which is the only thing the operators ask for. This is why learning the twenty or so operators once pays off across arrays, collections, JSON you have parsed and database rows. Note that the dictionary query sums values, so it does not depend on the order the dictionary hands out its entries.
// LINQ operators are extension methods on IEnumerable<T>, so the same
// calls work on anything that can be walked item by item.
List<string> cities = ["Lagos", "Oslo", "Lima", "Osaka"];
Dictionary<string, int> millions = new()
{
["Lagos"] = 15,
["Oslo"] = 1,
["Lima"] = 10,
["Osaka"] = 19
};
IEnumerable<string> oCities = cities.Where(c => c.StartsWith('O'));
// Enumerable.Range(start, count) builds a sequence that is not stored
// anywhere; it is produced as you walk it.
IEnumerable<int> squares = Enumerable.Range(1, 5).Select(n => n * n);
// Walking a dictionary gives KeyValuePair<string, int> values.
int total = millions.Where(entry => entry.Value > 5).Sum(entry => entry.Value);
Console.WriteLine(string.Join(", ", oCities));
Console.WriteLine(string.Join(", ", squares));
Console.WriteLine(total);
// Output:
// Oslo, Osaka
// 1, 4, 9, 16, 25
// 44
Discussion