Common LINQ operators
Filtering, projection, ordering, aggregation and the element operators, with the traps that produce exceptions in production.
The operators you actually use
| Category | Operators | Notes |
|---|---|---|
| Filtering | Where, OfType<T> | Deferred; runs when enumerated |
| Projection | Select, SelectMany | SelectMany flattens one level |
| Ordering | OrderBy, ThenBy, Reverse | ThenBy only after an OrderBy |
| Grouping | GroupBy, ToLookup | ToLookup executes immediately |
| Joining | Join, GroupJoin, Zip | Equality joins only |
| Set | Distinct, Union, Intersect, Except | Uses the default or supplied comparer |
| Partitioning | Take, Skip, TakeWhile, Chunk | Ordering must be explicit for paging |
| Element | First, Single, Last, ElementAt | Throwing and *OrDefault variants differ |
| Aggregation | Count, Sum, Min, Max, Average, Aggregate | Immediate; empty Min on values throws |
| Quantifiers | Any, All, Contains | Short-circuiting, unlike Count() > 0 |
Every operator above has a Func-based overload plus an overload taking an IEqualityComparer<T> or IComparer<T>. That second overload is how you control case-insensitive matching and domain-specific ordering.
Element and aggregation traps
// These throw on an empty or over-long sequence:
var a = nums.First(); // InvalidOperationException if empty
var b = nums.Single(); // throws unless exactly one element
var c = nums.Last(); // throws if empty
var d = nums.Max(); // throws on an empty sequence of values
var e = nums.ElementAt(10); // ArgumentOutOfRangeException
// Safe forms with an explicit fallback:
var a2 = nums.FirstOrDefault(-1);
var b2 = nums.SingleOrDefault();
var e2 = nums.ElementAtOrDefault(10);
// Prefer Any over Count for existence - Any short-circuits:
if (orders.Any(o => o.Total > 1000)) { /* ... */ }
bool allShipped = orders.All(o => o.Shipped);
// Aggregate folds a sequence into one value
var csv = names.Aggregate((acc, n) => acc + ", " + n);
var total = orders.Aggregate(
seed: 0m,
func: (sum, o) => sum + o.Total,
resultSelector: sum => Math.Round(sum, 2));Singleis a correctness assertion, not a convenience: it fails loudly when a uniqueness assumption is violated. Use it where duplicates would be a bug.Count() > 0enumerates the whole sequence;Any()stops at the first element. On anIQueryableboth become SQL, but the cost difference remains for LINQ to Objects.Average,MinandMaxthrow on an empty sequence for value types. Guard withAny()or project todecimal?first.Chunk(n)splits a sequence into fixed-size batches, which is the clean way to build bulk inserts.
Ordering and comparers
// multi-key ordering - ThenBy extends the previous OrderBy
var sorted = people
.OrderBy(p => p.LastName)
.ThenBy(p => p.FirstName)
.ThenByDescending(p => p.Hired)
.ToList();
// case-insensitive de-duplication with an explicit comparer
var uniqueEmails = users
.Select(u => u.Email)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
// top N per group without sorting the whole set
var bestPerCountry = orders
.GroupBy(o => o.Country)
.SelectMany(g => g.OrderByDescending(o => o.Total).Take(3))
.ToList();
// paging: Skip/Take is only stable with a deterministic order
var page3 = orders
.OrderBy(o => o.Placed).ThenBy(o => o.Id)
.Skip(2 * 25).Take(25)
.ToList();⚠️
Unordered
Skip/Take is nondeterministic. Rows can repeat or vanish between page requests because the database is free to return any order. Always pair paging with a total order that includes a unique column such as the primary key.FAQ
Is Count() or Any() better for checking emptiness?
Any(). It stops as soon as one element is found, while Count() must enumerate everything. On a database query both translate to an aggregate, but Any still produces the cheaper plan.Why did my OrderBy change nothing?
LINQ never mutates the source;
OrderBy returns a new, ordered sequence. If you discarded the return value, or the consumer re-sorts the result, the ordering is lost.Related
Query syntax and method syntax Deferred execution and querying a database
Last refreshed 2026-09-18.