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:
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:
Count
and First
.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:
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.
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.