Skip to content

LINQ to Objects and Deferred Execution

LINQ to Objects means using LINQ against in-memory collections such as arrays, lists, dictionaries, and other sequences that implement IEnumerable<T>. This is the best place to learn core LINQ behavior because the data is local and the execution model is easier to observe.

Original Microsoft Learn reference: Introduction to LINQ Queries in C#.

Why this chapter matters

Many LINQ mistakes come from not knowing when a query actually runs. Deferred execution is one of the most important LINQ ideas because it affects correctness, performance, and surprising behavior.

Building a query is not always executing a query

Many LINQ operators are deferred. They return a sequence object that describes work to do later. The actual work happens when the sequence is enumerated.

mermaid
flowchart LR
    A["Build query"] --> B["No full execution yet"]
    B --> C["Enumerate result"]
    C --> D["Query work happens now"]
csharp
List<int> numbers = [1, 2, 3, 4, 5];

IEnumerable<int> query = numbers.Where(number => number > 2);

numbers.Add(6);

foreach (int number in query)
{
    Console.WriteLine(number);
}

The output includes 6 because the query was not materialized when it was created. The Where filter ran when the foreach loop enumerated the query.

That is the key lesson: creating the query and running the query are often separate moments.

Immediate execution

Some operators force immediate execution because they need a final answer or a concrete collection.

  • ToList, ToArray, and ToDictionary materialize results
  • Count, Any, All, First, and Single compute answers immediately
  • Sum, Min, Max, and Average aggregate immediately
csharp
List<int> numbers = [1, 2, 3, 4, 5];

List<int> materialized = numbers.Where(number => number > 2).ToList();
numbers.Add(6);

Console.WriteLine(string.Join(", ", materialized));

This time the output does not include 6 because the query result was captured in a list before the source changed.

Deferred versus immediate execution

This distinction is easier to remember if you think of two categories:

  • deferred operators build query steps for later
  • immediate operators force a result now

Examples of deferred operators include Where, Select, and OrderBy.

Examples of immediate operators include ToList, Count, First, and Sum.

Projection shapes the result

Projection means transforming each input element into a different output value. Select is the most common projection operator.

csharp
var products = new[]
{
    new { Name = "Book", Price = 20m, Quantity = 2 },
    new { Name = "Pen", Price = 5m, Quantity = 5 }
};

var summaries = products.Select(product => new
{
    product.Name,
    Total = product.Price * product.Quantity
});

Projection is one of the reasons LINQ feels powerful. It lets you move from raw data to the exact shape needed by the next step.

Multiple enumeration matters too

If a deferred query is enumerated several times, its logic may run several times.

csharp
IEnumerable<int> query = numbers.Where(number => number > 2);

Console.WriteLine(query.Count());
Console.WriteLine(query.Count());

That may be fine for a small list, but it can be wasteful or surprising when the query is expensive.

If you need stable repeated access, materializing once may be better.

Why deferred execution matters

Deferred execution is useful because:

  • it avoids unnecessary work until results are needed
  • it allows query composition in stages
  • it can reflect updates to the source sequence

It can also surprise you because:

  • the query might run more than once if enumerated multiple times
  • side effects inside lambdas become harder to reason about
  • source mutations can change later results

Common mistakes

Avoid side effects inside LINQ queries. A query that mutates state while it filters or projects values becomes harder to understand and debug. If the work is effect-heavy, a regular loop is often a better fit.

Also be careful with multiple enumeration. If a query is expensive and you need the results more than once, materialize it deliberately.

Summary

  • LINQ to Objects works over in-memory IEnumerable<T> sequences
  • many LINQ operators are deferred until enumeration
  • immediate operators force results now
  • materialization captures a snapshot of the current results
  • understanding deferred execution is essential for avoiding subtle LINQ bugs

Practice

Create a query over a list, enumerate it once, change the source list, and enumerate it again. Then repeat the same experiment after adding ToList() and compare the behavior.

As a second exercise, identify one query in your own code where multiple enumeration could be a problem and explain whether materializing the result would improve clarity or performance.

Built with VitePress and published on GitHub Pages.