Method syntax is the form of LINQ you write by chaining method calls like .Where(...), .OrderBy(...), .Select(...) on a sequence. It's the shape the compiler actually emits when it sees query syntax, and it covers a wider set of operators because some have no keyword form at all. This chapter walks through how the chain is built, what role lambdas play in it, and when method syntax is the better tool for the job.
IEnumerable<T>LINQ's method syntax is built entirely on extension methods. Every operator (Where, Select, OrderBy, Take, Sum, and so on) lives as a static method on the System.Linq.Enumerable class, with a this IEnumerable<T> first parameter. That this modifier is what lets you call them as if they were instance methods on any sequence.
A stripped-down version of Where looks like this:
The this keyword on the first parameter turns Where into an extension method. With using System.Linq; in scope, the compiler lets you call it like an instance method:
The call prices.Where(...) is rewritten by the compiler to Enumerable.Where(prices, ...). They mean the same thing. The instance-call form is more readable and lets you chain operators left to right.
If you forget the using System.Linq; directive, the methods aren't in scope and the call fails with CS1061:
The error is misleading until you've seen it once. The method exists, it just isn't visible.
Extension methods are resolved at compile time. There's no runtime dispatch cost compared to a regular static call. The this parameter is syntactic sugar.
Most LINQ methods take a delegate as one of their parameters. Where takes Func<T, bool> (a predicate). Select takes Func<T, TResult> (a projector). OrderBy takes Func<T, TKey> (a key selector). You almost always supply these as lambda expressions written inline.
A lambda is a compact function literal. The parameters appear on the left of =>, and the body appears on the right.
When there's exactly one parameter, parentheses are optional:
The lambda p => p.StartsWith("M") is a Func<string, bool>. The compiler infers the parameter type from Where<string>'s signature, so you don't need to write (string p) =>.
When the delegate takes more than one parameter, you must use parentheses:
There's an overload of Select that takes a Func<TSource, int, TResult> so you get the element and its index. The parentheses around (rating, index) are required because there are two parameters.
When the logic needs more than one expression, switch to a block body. You write the body inside { ... } and use return explicitly.
Statement-bodied lambdas are fine for short multi-line logic, but if the body grows past three or four lines, pull it out into a named method. A long lambda inside a Select call is hard to read and hard to debug.
The expression form (the most common shape) skips the braces and the return keyword. The body is a single expression whose value is returned automatically.
This compiles to the same thing as:
Prefer the expression form whenever the logic fits in one expression. It's the convention in real LINQ code.
The real payoff of method syntax is chaining. Every LINQ operator (with a handful of terminal exceptions) returns an IEnumerable<T> or a specialized subtype like IOrderedEnumerable<T>. The return type is itself a sequence, which means you can immediately call another operator on it.
A simple chain over a list of orders:
Three operators, three lines, one operator per line. Each line takes the previous sequence as input and returns a new sequence. Reading top to bottom matches the order the data flows.
Here's what each call returns:
| Step | Operator | Input | Output type |
|---|---|---|---|
| 1 | .Where(t => t >= 20m) | List<decimal> | IEnumerable<decimal> |
| 2 | .OrderByDescending(t => t) | IEnumerable<decimal> | IOrderedEnumerable<decimal> |
| 3 | .Select(t => $"Order total: {t:C}") | IOrderedEnumerable<decimal> | IEnumerable<string> |
The chain doesn't run anything until foreach starts pulling values out. That's deferred execution. For now, just know that the chain only describes the work; the work happens when you iterate.
The data flow looks like this:
Each box is a stage in the pipeline. Items flow left to right. The chain reads top to bottom in code, which is the same direction the data moves through it. That alignment is part of why method syntax scales: you don't have to mentally rearrange the steps.
Each operator in a chain allocates one small wrapper object (an iterator). For typical chains over typical data, this is irrelevant. For tight inner loops over millions of elements, a hand-written for loop with the conditions inlined can be faster. Profile before switching to for.
When a chain grows past two or three operators, put each one on its own line, with the dot at the start of the line. Most C# developers and editors follow this style.
The leading dot makes it obvious where each step begins and lets you comment out a single operator by adding // to that line. Keeping one operator per line also makes diff-friendly history, since adding a new step doesn't touch any other line.
The compiler doesn't have a separate execution path for query syntax. It rewrites every query expression into method-syntax calls before compiling. Query syntax is, in the precise sense, sugar over method calls.
Take this query written in query syntax:
The compiler rewrites that exact query into this method-syntax chain:
Same result, same generated IL. If you put both side by side and decompile them, the output is identical. The difference is purely stylistic at the source level.
Because the rewrite is straightforward, you can read any query-syntax expression by mentally translating the keywords:
| Query keyword | Method-syntax equivalent |
|---|---|
from x in source | source (the receiver) |
where cond | .Where(x => cond) |
orderby key | .OrderBy(x => key) |
orderby key descending | .OrderByDescending(x => key) |
select expr | .Select(x => expr) |
group expr by key | .GroupBy(x => key, x => expr) |
join | .Join(...) |
let name = expr | (compiler synthesizes a Select to add the variable) |
Knowing this is useful even if you prefer query syntax, because compiler errors and the debugger always show you the method-syntax form.
Query syntax only supports a handful of keywords. Most LINQ operators have no keyword at all and can only be called through method syntax. The list is long; here are the common ones:
| Operator | What it does |
|---|---|
Skip(n) | Drop the first n elements |
Take(n) | Keep the first n elements |
Count() | Return the number of elements |
Sum() | Add all elements (numeric) |
Average() | Average of all elements (numeric) |
Min() / Max() | Smallest / largest element |
First() / FirstOrDefault() | First element (or default) |
Single() / SingleOrDefault() | The one and only element |
Any(pred) | Is there at least one matching element? |
All(pred) | Do all elements match? |
Distinct() | Drop duplicates |
Contains(value) | Is value in the sequence? |
ToList() / ToArray() | Run the chain and collect results |
Try writing any of those in query syntax. You can't. The keyword form doesn't include them, so you either drop into method syntax or wrap the query in parentheses and call them on the result.
A few in action over a cart:
Count, Sum, Max, Any, All, First, Distinct are all method-syntax-only. Count, Sum, Max, Any, All, and First are terminal: they return a single value, not a sequence. Once you call one, the chain ends. You can't keep chaining LINQ operators on a decimal or a bool.
Distinct is different. It returns a sequence (an IEnumerable<T>), so the chain can continue. That's why cart.Distinct().Count() works: Distinct produces a sequence, then Count terminates it.
When you want most of the query-syntax shape but need a method-syntax operator like Count or Take, you wrap the query in parentheses and call the method afterward.
The parentheses make the rule clear: everything inside is a query expression that produces an IEnumerable<T>, and then you call Count() or Take(3) on that. This is a clean way to combine the readability of query syntax with the breadth of method syntax. That said, once the chain has more than one or two method-syntax tail calls, most teams just rewrite the whole thing as method syntax. Mixing styles in every line gets noisy.
Method syntax isn't universally better than query syntax. They each fit different situations. Here's when method syntax is the clear choice:
Single-operator queries. When all you want is one filter or one projection, query syntax forces you to write from x in source select x just to wrap it. Method syntax is shorter.
Terminal aggregates. When the result is a scalar (count, sum, min, max, any, all), method syntax goes straight to the answer without needing a select clause.
The Count overload that takes a predicate is a nice shortcut. cart.Count(p => p > 50m) is the same as cart.Where(p => p > 50m).Count(), but it's shorter and the optimizer can sometimes do less work.
Operators with no keyword. Take, Skip, Distinct, Reverse, Zip, Concat, Union, Except, Intersect. Every one of these is method-syntax-only.
Partial application via captured lambdas. When a predicate or selector is reused across multiple queries, you can declare it once and pass it by name.
The inStock predicate is defined once and reused. Query syntax can't take a named delegate as cleanly; you'd have to invoke it explicitly inside a where.
Method chains that mix many operators. When the chain has four, five, or six operators, the one-operator-per-line method-syntax style is easier to read than the equivalent query expression with multiple let clauses and nested froms.
Combining everything so far: filtering, ordering, projecting, and aggregating over a small product catalog.
In the chain:
Where calls in a row are perfectly fine. The compiler doesn't merge them automatically (each call is a separate stage in the pipeline), but the readability win usually beats squeezing both conditions into one lambda.OrderBy comes before Take. If you swap them, you take three random items and then sort those three, which is rarely what you want. Order matters.Select runs last because it transforms the final shape. Doing the Select first would force OrderBy and Take to work on strings rather than Product instances, which is harder and slower.Count, Average, Max, Any) all run their own pass over the catalog. Four passes for four aggregates. If performance ever mattered, you'd compute them in a single fold (with a for loop or Aggregate), but for an in-memory list of seven items, the readability is more valuable.Each terminal operator (Count, Sum, Max, Average, Any, All) iterates the source once. Calling four of them in a row reads the source four times. For an in-memory collection this is usually fine; for a query that hits a database or a large file, it isn't.
A few small conventions that show up across real C# code:
IEnumerable<string>, IOrderedEnumerable<Product>, etc.) rarely matters at the call site, and var keeps the line short.ToList() does. If you're going to iterate the result exactly once with a foreach, you don't need it. The _Deferred vs Immediate Execution_ lesson covers the trade-offs.cart.Count(p => p > 50m) is preferred over cart.Where(p => p > 50m).Count(). Same result, less code.10 quizzes