Codectionary / Developer documentation / C#

LINQ Deferred Execution

Most LINQ operators (Where, Select, OrderBy, etc.) use deferred execution - they don't actually run when you write the query, only when you enumerate the result, such as with a foreach loop or a call to ToList(). This means a deferred query re-runs every time you enumerate it, picking up any changes to the underlying data source since it was defined, which can be surprising if not understood.

Syntax

var query = collection.Where(predicate);  // not executed yet

Examples

Queries Are Not Executed Immediately

Defining a query does not run it - only enumerating it does.

List<int> numbers = new List<int> { 1, 2, 3 };

var query = numbers.Where(n =>
{
    Console.WriteLine($"Checking {n}");
    return n > 1;
});

Console.WriteLine("Query defined, but not run yet");
var result = query.ToList();  // THIS is when "Checking..." actually prints

A Deferred Query Re-Reads the Source

Because execution is deferred, changes to the source between enumerations are picked up.

List<int> numbers = new List<int> { 1, 2, 3 };
var evens = numbers.Where(n => n % 2 == 0);

numbers.Add(4);
numbers.Add(6);

Console.WriteLine(string.Join(", ", evens));  // 2, 4, 6 - includes the new items!

Forcing Immediate Execution

Calling ToList(), ToArray(), or Count() forces the query to run right away and captures a snapshot.

List<int> numbers = new List<int> { 1, 2, 3 };
List<int> evensSnapshot = numbers.Where(n => n % 2 == 0).ToList();  // runs NOW

numbers.Add(4);

Console.WriteLine(string.Join(", ", evensSnapshot));  // 2 - unaffected by the later Add

Best practices

  • Call .ToList() or .ToArray() when you need a stable snapshot of results that will not change if the underlying source is modified later
  • Be cautious about enumerating the same deferred query multiple times when the source (or the query itself) is expensive to compute - each enumeration re-runs the whole pipeline
  • Remember methods like Count(), Sum(), First(), and ToList() all trigger immediate execution of everything before them in the chain
  • Watch for 'multiple enumeration' bugs where a deferred query is enumerated more than once unintentionally (e.g. once in an if check, again in a loop) - materialize it first with .ToList() to avoid redundant work

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

Lambda Expressions
A lambda expression is a compact way to represent an anonymous function - a block of code that can be passed around as a value, using the => (goes to) operator. Lambdas work with delegate types and functional interfaces like Func<> and Action<>, letting you write inline logic without a full named method. Lambdas are the foundation that makes LINQ's fluent, expressive query syntax possible.
LINQ Basics: Where & Select
LINQ (Language Integrated Query) provides a consistent, declarative way to query collections, databases, and XML directly within C# syntax. Where() filters a sequence down to elements matching a condition, and Select() transforms each element into something new (a projection). Both are 'lazy' - they build a query that only actually runs when you iterate over the result or call something like ToList().
LINQ Ordering & Grouping
OrderBy() and OrderByDescending() sort a sequence by a key you specify, with ThenBy() for secondary sort keys on ties. GroupBy() splits a sequence into groups based on a key selector, producing a sequence of groups where each group itself is enumerable and has a Key property identifying which group it represents.
LINQ Aggregation: Sum, Count, Average
LINQ provides built-in aggregation methods that reduce a sequence down to a single summary value: Count() for the number of elements, Sum() for a total, Average() for the mean, and Max()/Min() for extremes. Aggregate() is the general-purpose version, letting you define fully custom accumulation logic when the built-in methods aren't enough.