Reinventing The Wheel

LINQ's Deferred Execution

Most LINQ methods use deferred execution, which means the query is only executed when it comes time to iterating through the collection.

There are 3 benefits to using deferred executing:

  • Increased efficiency
  • Infinite series
  • Up-to-date data

Increased efficiency

You're able to linq together different methods, and not have each method enumerate the collection. Here's an example:

var results = collection.Select(item => item.Foo).Where(foo => foo < 4).ToList();

Here we have a Select, a Where and a ToList call. The only time the collection is enumerated is when the ToList() method is called. Here's the equivalent code using a foreach loop.

List<Foo> results = new List<Foo>();
foreach(var item in collection)
{
    // "Select" does a mapping
    var foo = item.Foo; 

    // "Where" filters
    if (!(foo < 4))
    {
         continue;
    }

    // "ToList" builds results
    results.Add(foo);
}

If Linq didn't used deferred execution, then the code would be inefficient as the collection would be enumerated each method call:

// Select
List<Foo> foos = new List<Foo>();
foreach(var item in collection)
{
    foos.Add(item.Foo);
}

// Where
List<Foo> foosFiltered = new List<Foo>();
foreach(var foo in foos)
{
    if (foo < 4)
    {
        foosFiltered.Add(foo);
    }
}    

List<Foo> results = new List<Foo>();
foreach(var item in foosFiltered)
{
    results.Add(item);
}

Infinite series

If Linq executed methods immediately, the following code would result in an OverflowException.

When FibonacciNumbers() is called (right before .Take(4)), it isn't executed yet. If it was like with most methods, then it would be stuck in an infinite loop.

Thanks to deferred execution, Linq knows to take only the first 4 items, BEFORE it enumerates the method.

Calling ToList() forces the method to be enumerated, and only 4 items are returned.

IEnumerable<UInt64> fibNums = FibonacciNumbers().Take(4).ToList();

public static IEnumerable<UInt64> FibonacciNumbers()
{
    yield return 0;
    yield return 1;

    UInt64 previous = 0, current = 1;
    while (true)
    {
        UInt64 next = checked(previous + current);        
        yield return next;
        previous = current;
        current = next;
    }
}

Up-to-date data

The following code prints 1 to 5, not just 1 to 4 as it would appear at first glimpse. Deferred execution means we get the most up-to-date data right before we enumerate the collection.

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

IEnumerable<int> filteredCollection = from x in collection select x;

collection.Add(5);

foreach (var i in filteredCollection)
{
    Console.WriteLine(i);
}