๐ŸŽ“ A1Learner
๐Ÿ”
0 of 38 lessons completed

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?