Most of the query operators in LINQ have a very important feature: deferred execution. This means that they are not executed when the query is created, but rather when they are traversed (in other words, when the enumerator MoveNext method is invoked). Let's consider the following query:
Static voidTestdeferredexecution ()
{
varNumbers =Newlist<int> ();
Numbers. ADD (1);
ienumerable<int> query = numbers. Select (n = n *Ten);//Build Query
Numbers. ADD (2);//Add an extra element after the query
foreach(intNinchQuery
Console.Write (n +"|");//10|20|
}
As you can see, the number that we added after the query was created is also included in the query results, because the LINQ query does not execute until the foreach statement iterates over query, and the data source numbers already contains the element 2 that we added later. This feature of LINQ is deferred execution. Except for the following two query operators, all other operators are deferred:
- A query operator that returns a single element or a scalar value, such as first, count, and so on.
- The following conversion operators are: ToArray, ToList, ToDictionary, ToLookup.
The above two operators are executed immediately, because their return value type does not provide a mechanism for deferred execution, such as the following query is executed immediately.
int 2 0). Count (); // 1
For LINQ, deferred execution is important because it decouples the creation of queries from the execution of queries, which allows us to create our LINQ queries in multiple steps as we create SQL queries.
Repeated execution
One effect of deferred execution is that when we iterate through the query results repeatedly, the query is executed repeatedly:
Static voidTestreevaluation ()
{
varNumbers =Newlist<int> () {1,2};
ienumerable<int> query = numbers. Select (n = n *Ten);//Build Query
foreach(intNinchQuery) Console.Write (n +"|");//10|20|
Numbers. Clear ();
foreach(intNinchQuery) Console.Write (n +"|");//<nothing>
}
Sometimes, repeated execution is not an advantage for us, for the following reasons:
- When we need to save the results of a query at a given point.
- Some queries are time-consuming, such as when querying a very large sequence or fetching data from a remote database, for performance reasons, we don't want a query to be executed repeatedly.
At this point, we can use the conversion operators described earlier, such as ToArray, tolist to avoid repeated execution, ToArray save the query results to an array, and tolist save the results to the generic list<>:
Static void Testdefeatreevaluation ()
{
var New list<int12 };
list<int> TimesTen = Numbers
Ten)
. ToList (); // executes immediately into a list<int>
Numbers. Clear ();
Console.Write (timesten.count); // still 2
}
Variable capture
Delayed execution also has a bad side effect. If a query's lambda expression references a program's local variables, the query captures the variables at execution time. This means that if the value of the variable is changed after the query definition, the query results will change as well.
Static voidTestcapturedvariable ()
{
int[] numbers = {1,2};
intFactor =Ten;
ienumerable<int> query = numbers. Select (n = n * factor);
Factor = -;
foreach(intNinchQuery
Console.Write (n +"|");//20|40|
}
This feature becomes a real trap when we create a query through the Foreach loop. If we want to get rid of all the vowels in a string, we might write the following query:
ienumerable<Char> query ="How is you, friend.";
query = query. Where (c + = c! ='a');
query = query. Where (c + = c! ='e');
query = query. Where (c + = c! ='I');
query = query. Where (c + = c! ='o');
query = query. Where (c + = c! ='u');
foreach(CharCinchQuery) Console.Write (c);//Hw R y, frnd.
Although the program results are correct, we can see that the program is not elegant enough. So it's natural to think of using a Foreach loop to refactor the above program:
ienumerable<" how is You, friend. ;
foreach (char Vowel in aeiou ")
Query = query. Where (c + = c! = vowel);
foreach (char C in query) Console.Write (c); // how is Yo, friend.
The result is only the letter U is filtered, I see, there is no surprise! But if you think about it, you'll know why: because vowel is defined outside the loop, each lambda expression captures the same variable. When our query executes, what is the value of vowel? is not the filter of the letter U. To solve this problem, we simply assign the loop variable to an internal variable, such as the following temp variable scope is just the current lambda expression.
ienumerable<char"Howis is you, friend. " ";
foreach (charin"aeiou")
{
char temp = vowel;
query = query. Where (c + = c! = temp);
}
foreach (charin//Hw R y, frnd.
Implementation principle of deferred execution
The query operator supports deferred execution by returning the adorner sequence (decorator sequence).
Unlike traditional collection types such as the array,linked list, a decorator sequence does not have the underlying structure to hold the element itself, but instead wraps another sequence that we provide at run time. Then when we request data from decorator sequence, it will instead request data from the wrapper's sequence.
Call where, for example, creates a decorator sequence, which holds references to input sequence, lambda expressions, and other supplied parameters. The following query corresponds to the decorator sequence:
ienumerable<intnewint53);
As we traverse the lessthanten, we are actually looking through the where decorator to find the data from the array.
The query operator link creates a multilayer decorator, and each query operator instantiates an adorner to wrap the previous sequence, such as the following query and the corresponding multilayer decorator sequence:
ienumerable<intnewint53 }
Ten)
. (n = n)
( ten);
As we traverse the query, we are actually querying the original array through a decorator chain.
Note that if you add a conversion operator, such as ToList, after the query above, then query will be executed immediately so that the single list will replace the entire object model above.
LINQ Route 6: Deferred Execution (Deferred execution)