C# Deferred Execution
Deferred execution means a query runs at the moment a program reads its result. Defining a query stores the steps and runs nothing yet.
A late change still counts
The query is defined before 10 joins the list. The loop runs after the change and prints 10 with the rest.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1);
numbers.Add(10);
foreach (int n in query)
{
Console.WriteLine(n);
}
}
}A snapshot and a live query
ToList freezes one run into a snapshot list. A second change reaches the live query and skips the snapshot.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1);
numbers.Add(10);
List<int> snapshot = query.ToList();
numbers.Add(20);
int snapshotCount = 0;
foreach (int n in snapshot)
{
snapshotCount = snapshotCount + 1;
}
int liveCount = 0;
foreach (int n in query)
{
liveCount = liveCount + 1;
}
Console.WriteLine("Snapshot: " + snapshotCount);
Console.WriteLine("Live: " + liveCount);
}
}Each read runs the query again
The first loop prints the two matches in the list. A new match joins the list and the second loop prints all three.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<int> numbers = new List<int> { 6, 2, 9 };
var query = numbers.Where(n => n > 5);
Console.WriteLine("First:");
foreach (int n in query)
{
Console.WriteLine(n);
}
numbers.Add(12);
Console.WriteLine("Second:");
foreach (int n in query)
{
Console.WriteLine(n);
}
}
}Example
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<int> values = new List<int> { 2, 4, 6 };
var query = values.Select(n => n * 3);
List<int> saved = query.ToList();
values.Add(8);
Console.WriteLine("Saved:");
foreach (int n in saved)
{
Console.WriteLine(n);
}
Console.WriteLine("Live:");
foreach (int n in query)
{
Console.WriteLine(n);
}
}
}Key points
- A query holds a set of steps for building a result.
- The query runs each time a program reads from it.
- A later read sees changes made to the source after the definition.
- ToList runs a query at once and stores the result in a list.
- A list from ToList is a snapshot of one single run.
- Later source changes reach the live query but not the snapshot.
In this example, ToList runs the query and saves 6, 12 and 18. The value 8 joins the source after that run. The saved list prints the old three values and the live query prints all four.
Note
A foreach loop is one way to read a query and run it.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does ToList do to a query?