last modified May 14, 2025
This tutorial explores LINQ deferred execution in C#, a key concept that controls when queries are evaluated. You'll learn how deferred execution works, its benefits, and how to manage query execution effectively.
Deferred execution, also known as lazy evaluation, delays LINQ query execution until the results are needed. This approach allows for flexible query composition, optimization, and efficient resource usage, especially when working with large datasets or databases.
Key benefits of deferred execution:
However, deferred execution requires careful handling, as queries are re-evaluated each time they are enumerated, which can lead to unexpected results or performance issues if not managed properly.
A simple example demonstrates how deferred execution delays query evaluation until enumeration.
List<int> numbers = [1, 2, 3, 4, 5];
// Query definition - not executed yet
var query = from n in numbers
where n % 2 == 0
select n;
Console.WriteLine("Query created");
// Modify the data source
numbers.Add(6);
numbers.Add(7);
numbers.Add(8);
// Query execution happens here
Console.WriteLine("Query results:");
foreach (var num in query)
{
Console.WriteLine(num);
}
The query is defined but not executed until the foreach loop.
Modifications to numbers before enumeration affect the results.
var query = from n in numbers where n % 2 == 0 select n;
This LINQ query creates an IQueryable object that represents
the query logic but does not execute it immediately.
$ dotnet run Query created Query results: 2 4 6 8
LINQ methods are divided into those that use deferred execution and those that trigger immediate execution.
List<string> fruits = ["apple", "banana", "cherry", "date"];
// Deferred execution
var deferredQuery = fruits.Where(f => f.Length > 5);
// Immediate execution
var immediateResult = fruits.Count(f => f.Length > 5);
fruits.Add("elderberry");
fruits.Add("fig");
Console.WriteLine("Deferred query results:");
foreach (var fruit in deferredQuery)
{
Console.WriteLine(fruit);
}
Console.WriteLine($"\nImmediate count result: {immediateResult}");
Methods like Where and Select defer execution,
while Count, ToList, and ToArray
force immediate execution.
$ dotnet run Deferred query results: banana cherry elderberry Immediate count result: 2
Deferred queries are re-evaluated each time they are enumerated, which can lead to different results if the data source or logic changes.
Random random = new();
var numbers = Enumerable.Range(1, 5).Select(n => random.Next(1, 100));
var query = numbers.Where(n => n % 2 == 0);
Console.WriteLine("First enumeration:");
foreach (var num in query) Console.WriteLine(num);
Console.WriteLine("\nSecond enumeration:");
foreach (var num in query) Console.WriteLine(num);
Each enumeration generates new random numbers, producing different results.
$ dotnet run First enumeration: 42 88 Second enumeration: 56 24
Use conversion methods like ToList or ToArray to
force immediate query execution and cache results.
List<int> data = [10, 20, 30, 40, 50];
// Deferred execution
var deferredQuery = data.Select(n => {
Console.WriteLine($"Processing {n}");
return n * 2;
});
// Force immediate execution
var immediateList = deferredQuery.ToList();
Console.WriteLine("\nResults:");
foreach (var num in immediateList)
{
Console.WriteLine(num);
}
ToList executes the query immediately, and subsequent
enumerations use the cached results.
$ dotnet run Processing 10 Processing 20 Processing 30 Processing 40 Processing 50 Results: 20 40 60 80 100
LINQ method chains maintain deferred execution until the final enumeration, allowing complex query composition.
List<string> words = ["sky", "blue", "cloud", "forest", "ocean"];
var query = words
.Where(w => w.Length > 3)
.OrderBy(w => w)
.Select(w => w.ToUpper());
words.Add("river");
foreach (var word in query)
{
Console.WriteLine(word);
}
The entire chain is evaluated only during enumeration, including the newly added "river".
$ dotnet run BLUE CLOUD FOREST OCEAN RIVER
Deferred execution is useful for processing nested collections, such as filtering inner collections dynamically.
var departments = new[]
{
new { Name = "HR", Employees = new[] { "Alice", "Bob" } },
new { Name = "IT", Employees = new[] { "Charlie", "Dave" } }
};
var query = from dept in departments
let activeEmployees = dept.Employees.Where(e => e.Length > 4)
where activeEmployees.Any()
select new { dept.Name, ActiveEmployees = activeEmployees };
foreach (var dept in query)
{
Console.WriteLine($"{dept.Name}: {string.Join(", ", dept.ActiveEmployees)}");
}
The inner collection is filtered only when the query is enumerated, reflecting the latest data.
$ dotnet run HR: Alice IT: Charlie
Deferred execution is critical for database queries, minimizing round-trips to the database.
var dbQuery = dbContext.Products
.Where(p => p.Price > 100)
.OrderBy(p => p.Name);
// Additional filtering can be added later
if (categoryFilter != null)
{
dbQuery = dbQuery.Where(p => p.Category == categoryFilter);
}
// Query executes only when materialized
var results = dbQuery.ToList();
The query is built incrementally and sent to the database only when
ToList is called.
To use deferred execution effectively:
ToList or
ToArray to store results if the query is expensive or needs
consistent output.Deferred Execution vs. Lazy Evaluation in LINQ
This tutorial covered the essentials of LINQ deferred execution in C#, including its mechanics, benefits, and practical applications.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all C# tutorials.