Menu

C# LINQ: Where, Select, OrderBy, GroupBy and More

LINQ adds query operators to every collection in C#. Learn method syntax and query syntax, filtering with Where, projecting with Select, sorting with OrderBy and ThenBy, grouping with GroupBy, the First vs FirstOrDefault trap, aggregates, and how deferred execution changes when your query runs.

This page includes runnable editors - edit, run, and see output instantly.

LINQ (Language Integrated Query) is a library of methods for filtering, transforming, sorting, grouping and summarizing sequences. Add using System.Linq; and every array, List<T>, Dictionary and other IEnumerable<T> gains methods like Where, Select and OrderBy, each taking a lambda that says what to do with one element.

A first query: method syntax and query syntax

LINQ has two notations. Method syntax chains calls; query syntax reads like SQL. The compiler turns query syntax into the same method calls, so they produce identical results.

Output:

95, 91, 88
95, 91, 88

Method syntax is more common in modern code, and some operators (Take, Distinct, Count, First, ToList) exist only as methods. Query syntax shines for joins and for queries that introduce intermediate variables with let. The rest of this page uses method syntax and shows query syntax where it adds something.

Where and Select: filter and project

Where keeps the elements that match a condition. Select turns each element into something else. Together they cover most everyday queries.

Output:

Mug, Kettle
MUG $8.50 | LAMP $32.00 | KETTLE $27.00 | RUG $120.00
Lamp: 38.40
Rug: 144.00
1. Mug 2. Lamp 3. Kettle 4. Rug

Where never changes elements, and Select never changes how many there are. new { p.Name, WithTax = ... } creates an anonymous type, a class the compiler writes for you, whose properties you read with normal dot syntax. It has no name you can write, so a variable holding one must be declared with var, and it suits results that stay inside one method.

OrderBy, ThenBy and sorting by several keys

Output:

Ana 60
Ana 15
Ben 90
Ben 40
Chloe 25
Ben, Ana, Ben, Chloe, Ana

OrderBy is stable: elements with equal keys stay in their original order. Use ThenBy or ThenByDescending for a second key. A second OrderBy would re-sort everything by its own key, leaving the first key as a mere tie breaker: the opposite priority. In query syntax, the keys are comma-separated: orderby o.Customer, o.Total descending.

Unlike List<T>.Sort, OrderBy leaves the source untouched and returns a new sequence.

First, Single, Last and their OrDefault versions

These return one element instead of a sequence. The plain versions throw when there is nothing to return; the OrDefault versions return the type's default value.

Output:

Ana
Ben
Bea
True
First: InvalidOperationException
Single: InvalidOperationException
0

The exception from First on an empty sequence is InvalidOperationException with the message "Sequence contains no elements" (or "Sequence contains no matching element" when a predicate matched nothing), one of the most searched C# errors. Use FirstOrDefault when "none" is a normal outcome and check for null.

The last line shows the value type trap: for an int sequence, FirstOrDefault returns 0 when nothing matches, which cannot be told apart from a real 0. Filter with Where and check Any() first, or on .NET 6 and later pass your own default: scores.FirstOrDefault(s => s > 100, -1).

Single is for "exactly one": it throws if there are none or more than one, which makes it an assertion as much as a query.

Any, All, Count and Contains

Output:

True
True
True
3
True
True

Any() stops at the first match, so items.Any() is the right way to ask "is there anything?". items.Count() > 0 reads the Count property when the source is a collection such as a list or array, but on a query or other lazy sequence it walks every element first. All on an empty sequence returns true, because there is no element that fails the condition.

Sum, Average, Min and Max

Output:

Total: 28.25
Items: 7
Priciest: 7.25
Average price: 4.92
0
Average: InvalidOperationException

Sum of an empty sequence is 0, but Average, Min and Max of an empty sequence of numbers throw InvalidOperationException: there is no sensible answer. Guard with Any(), or use DefaultIfEmpty() first: empty.DefaultIfEmpty(0).Average().

Distinct, Take and Skip

Output:

home, shop, cart
Page 1: 1 2 3 4 5 6 7 8 9 10
Page 2: 11 12 13 14 15 16 17 18 19 20
Page 3: 21 22 23
12, 15

Distinct keeps the first occurrence of each value in order. Skip(n).Take(m) is the standard paging pattern, and TakeWhile/SkipWhile stop or start at the first element that fails a condition. Enumerable.Range(start, count) generates a sequence of integers without an array.

GroupBy

GroupBy sorts elements into buckets by a key. Each group has a Key and is itself a sequence, so you can aggregate it.

Output:

North: 3 sales, total 600
South: 2 sales, total 400
East: 1 sales, total 350
Top rep: Ana 400
North -> Ana/Chloe/Ana; South -> Ben/Eli; East -> Dev

Groups come out in the order their keys first appear in the source (North, South, East), not sorted; add OrderBy(g => g.Key) if you need that. In query syntax, group X by K chooses what goes into each group (here just the rep's name) and into g names the group so the query can continue.

ToList, ToArray and ToDictionary

The To... methods run the query and store the results.

Output:

ben@x.com
2
ToDictionary: ArgumentException

ToDictionary throws ArgumentException when two elements produce the same key, just like Dictionary.Add. If duplicates are expected, group first (as wordCounts does) or use ToLookup, which maps each key to a sequence of values.

Deferred execution

Where, Select, OrderBy, GroupBy and most other operators return a query object and do no work until something enumerates it. That has two consequences worth seeing:

Output:

25, 40, 99
  converting 10
  converting 25
  converting 40
  converting 99
Count: 4
  converting 10
  converting 25
  converting 40
  converting 99
Max: 9900
  converting 10
  converting 25
  converting 40
  converting 99
Cached: 4, 9900

The first query saw 99 even though it was added after the query was written: the filter ran when string.Join enumerated it. The second query printed its conversions twice because Count() and Max() each enumerated it from scratch. With a cheap lambda that is only wasted work; with a database query, a file read, or a random number inside the lambda, the two passes can return different data. When you will use a result more than once, materialize it with ToList() or ToArray().

Operators that return a single value (Count, Sum, First, Any, Max) and the To... methods run immediately. See IEnumerable and yield for how that laziness is built.

Quick reference

OperatorDoesReturns
Where(x => cond)FilterSequence (deferred)
Select(x => expr)Transform eachSequence (deferred)
SelectMany(x => seq)Flatten nested sequencesSequence (deferred)
OrderBy, ThenBy (+Descending)Sort, stableSequence (deferred)
GroupBy(x => key)Bucket by keySequence of groups (deferred)
Distinct, Take, SkipDedupe, pageSequence (deferred)
First, Single, LastOne element, throws if noneElement
FirstOrDefault etc.One element or defaultElement
Any, All, ContainsTestbool
Count, Sum, Min, Max, AverageAggregateNumber
ToList, ToArray, ToDictionaryRun and storeCollection

Common mistakes

  • First() on a sequence that can be empty. Throws "Sequence contains no elements"; use FirstOrDefault and check.
  • Two OrderBy calls. The second key becomes the main one; use ThenBy.
  • Enumerating a query several times. The work repeats; call ToList() once.
  • Expecting a query to snapshot its source. It reads the source when enumerated, not when written.
  • Average, Min, Max on an empty sequence. They throw; guard with Any() or DefaultIfEmpty.
  • Count() > 0 to test for elements. Any() stops at the first one.

Frequently Asked Questions

What is LINQ in C#?

LINQ (Language Integrated Query) is a set of extension methods in System.Linq, such as Where, Select, OrderBy and GroupBy, that work on any IEnumerable<T>: arrays, lists, dictionaries, and results of other queries. C# also has a query syntax (from x in items where ... select ...) that the compiler translates into the same method calls.

What is the difference between Select and Where in LINQ?

Where filters: it keeps the elements for which the lambda returns true and leaves them unchanged. Select projects: it transforms every element into something else, such as a property or a new object, and keeps the count the same. They are often chained: people.Where(p => p.Age >= 18).Select(p => p.Name).

What is the difference between First and FirstOrDefault?

First() returns the first element (or the first match for a predicate) and throws InvalidOperationException ("Sequence contains no elements" or "no matching element") when there is none. FirstOrDefault() returns the type's default value instead: null for classes and 0 for int, so check the result before using it.

How do I sort by two fields with LINQ?

Chain ThenBy after OrderBy: orders.OrderBy(o => o.Customer).ThenByDescending(o => o.Total). Calling OrderBy twice does not work: the second call re-sorts the whole sequence, so its key becomes the primary one and the first key only breaks ties, the reverse of what was meant.

How does GroupBy work in LINQ?

GroupBy(x => key) returns one group per distinct key. Each group is an IGrouping<TKey, TElement>: it has a Key property and is itself a sequence of the elements with that key, so you can call Count(), Sum() or Select on it. Groups come out in the order their keys first appear.

What is deferred execution in LINQ?

Most LINQ operators do not run when you call them; they return a query that runs each time it is enumerated (by foreach, ToList(), Count(), and so on). A query therefore sees changes made to the source after it was defined, and enumerating it twice does the work twice. Call ToList() or ToArray() to run it once and keep the results.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED