Monday 1 October 2012

Deferred Execution in LINQ Query Expression or Standard Query Operators Extension Methods

An IQueryable and IEnumerable type variable can be used to specify a query in query or method syntax (query syntax is also known as query expression while method syntax as standard query operators extension methods; see this MSDN article for definition). However the execution will take place when the items are required.

Methods such as Count(), ToList(), ToArray(), ToDictionary() or ToLookup() will iterate through items thus executing the query definition. Other methods such as Average(), Sum(), Max() and Min() will also execute the query definition.

If one of those methods mentioned above are called twice then the query definition will be executed again to get the result. The execution can be very expensive as the result may be retrieved from database, network, etc. This is done to get fresh result every time from the source.

To avoid re-executing the query definition in a query expression or standard query operators extension methods, store the result into a collection. When obtaining result from the collection, the query definition won't be run again rather the cached/stored result will be used.

Below is an example to clarify:
static void Main(string[] args)
{
    List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

    int invocationsCount = 0;
    Func<int, int> func = number =>
    {
        invocationsCount++;
        return number;
    };

    // 1. define the query expression or extension methods (method invocations)
    IEnumerable<int> selection = from n in numbers
                                    select func(n);
    //IEnumerable<int> selection = numbers.Select(n => func(n));
    Console.WriteLine("1. invocationsCount = {0}", invocationsCount);

    // 2. do a loop
    foreach (var item in selection)
    {
    }
    Console.WriteLine("2. invocationsCount = {0}", invocationsCount);

    // 3. do a count
    selection.Count();
    Console.WriteLine("3. invocationsCount = {0}", invocationsCount);

    // 4. do an average
    selection.Average();                
    Console.WriteLine("4. invocationsCount = {0}", invocationsCount);

    // 5. do another loop
    foreach (var item in selection)
    {
    }
    Console.WriteLine("5. invocationsCount = {0}", invocationsCount);

    // 6. do ToList() and cache it to a collection
    List<int> collection = selection.ToList();
    Console.WriteLine("6. invocationsCount = {0}", invocationsCount);

    // 7. do the loop on the cache collection
    foreach (var item in collection)
    {
    }
    Console.WriteLine("7. invocationsCount = {0}", invocationsCount);


    Console.ReadLine();
}

No comments: