Deferring LINQ execution

LINQ implements a deferred execution concept when we query the data from a collection. This means that the query will not be executed in the constructor time but in the enumeration process instead. For example, we use the Where operator to query data from a collection. Actually, the query is not executed until we enumerate it. We can use the foreach operation to call the MoveNext command in order to enumerate the query. To discuss deferred execution in further detail, let's take a look at the following code, which we can find in the DeferredExecution.csproj project:

public partial class Program 
{ 
  public static void DeferredExecution() 
  { 
    List memberList = new List() 
    { 
      new Member 
      { 
        ID = 1, 
        Name = "Eddie Morgan", 
        Gender = "Male", 
        MemberSince = new DateTime(2016, 2, 10) 
      }, 
      new Member 
      { 
        ID = 2, 
        Name = "Millie Duncan", 
        Gender = "Female", 
        MemberSince = new DateTime(2015, 4, 3) 
      }, 
      new Member 
      { 
        ID = 3, 
        Name = "Thiago Hubbard", 
        Gender = "Male", 
        MemberSince = new DateTime(2014, 1, 8) 
      }, 
      new Member 
      { 
        ID = 4, 
        Name = "Emilia Shaw", 
        Gender = "Female", 
        MemberSince = new DateTime(2015, 11, 15) 
      } 
    }; 
    IEnumerable<Member> memberQuery = 
      from m in memberList 
      where m.MemberSince.Year > 2014 
      orderby m.Name 
      select m; 
      memberList.Add(new Member 
      { 
        ID = 5, 
        Name = "Chloe Day", 
        Gender = "Female", 
        MemberSince = new DateTime(2016, 5, 28) 
      }); 
    foreach (Member m in memberQuery) 
    { 
      Console.WriteLine(m.Name); 
    } 
  } 
} 

As you can see in the implementation of the preceding DeferredExecution() method, we construct a List<Member> member list named memberList, which contains four instances of every member who has joined to the club. The Member class itself is as follows:

public class Member 
{ 
  public int ID { get; set; } 
  public string Name { get; set; } 
  public string Gender { get; set; } 
  public DateTime MemberSince { get; set; } 
} 

After constructing memberList, we query the data from memberList, which includes the all members who joined after 2014. Here, we can confirm that only three of four members satisfy the requirements. They are Eddie Morgan, Millie Duncan, and Emilia Shaw in ascending order, of course, since we use the orderby m.Name phrase in the query.

After we have the query, we add a new member to memberList and then run the foreach operation in order to enumerate the query. What will happen next is that, because most of the query operators implement deferred execution, which will be executed only in the enumeration process, we will have four members after enumerating the query since the last member we add to memberList satisfies the query requirement. To make this clear, let's take a look at the following output we get on the console after invoking the DeferredExecution() method:

Deferring LINQ execution

As you can see, Chloe Day, who is the last member to have joined the club, is included in the query result as well. This is where the deferred execution plays its role.

Almost all query operators provide deferred execution but not the operators that:

  • Return a scalar value or a single element, such as Count and First.
  • Convert the result of a query, such as ToList, ToArray, ToDictionary, and ToLookup. They are also called conversion operators.

The Count() and First() method will be executed immediately since they return a single object, so it's almost impossible to provide deferred execution as well as conversion operators. Using the conversion operator, we can obtain a cached copy of the query results and can avoid repeating the process due to the reevaluate operation in deferred execution. Now, let's take a look at the following code, which we can find in the NonDeferredExecution.csproj project, to demonstrate the nondeferred execution process:

public partial class Program 
{ 
  private static void NonDeferred() 
  { 
    List<int> intList = new List<int> 
    { 
      0,  1,  2,  3,  4,  5,  6,  7,  8,  9 
    }; 
    IEnumerable<int> queryInt = intList.Select(i => i * 2); 
    int queryIntCount = queryInt.Count(); 
    List<int> queryIntCached = queryInt.ToList(); 
    int queryIntCachedCount = queryIntCached.Count(); 
    intList.Clear(); 
    Console.WriteLine( 
      String.Format( 
        "Enumerate queryInt.Count {0}.", queryIntCount)); 
    foreach (int i in queryInt) 
    { 
      Console.WriteLine(i); 
    } 
    Console.WriteLine(String.Format( 
      "Enumerate queryIntCached.Count {0}.",
      queryIntCachedCount)); 
    foreach (int i in queryIntCached) 
    { 
      Console.WriteLine(i); 
    } 
  } 
} 

First, in the preceding code, we have a List<int> integer list named intList, which contains numbers from 0 to 9. We then create a query named queryInt in order to select all members of intList and multiply them by 2. We also count the total of the query data using the Count() method. Since queryInt is not executed yet, we create a new query named queryIntCached, which converts queryInt to List<int> using the ToList() conversion operator. We also count the total of the data in that query. We have two queries now, queryInt and queryIntCached. We then clear intList and enumerate the two queries. The following is the result of them being displayed on the console:

Deferring LINQ execution

As you can see in the preceding console, the enumeration of queryInt results in no item. This is clear since we have removed all intList items, so queryInt will find no item in intList. However, queryInt is counted as ten items since we have run the Count() method before we clear intList, and the method is immediately executed right after we construct it. In contrast to queryInt, we have ten items' data when we enumerate queryIntCached. This is because we have invoked the ToList() conversion operator and it is immediately executed as well.

Note

There is one more type of deferred execution. It happens when we chain the OrderBy method after a Select method, for instance. The Select method will only retrieve one element at the time that it has to produce an element, while the OrderBy method has to consume the entire input sequence before it returns the first element. So, when we chain an OrderBy method after the Select method, the execution will be deferred until we retrieve the first element, and then the OrderBy method will ask Select for all the elements.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset