|
|
|
|
Deferred Query Evaluation and Extension
Methods Resolution
Conversion operators allow the results of a query to be represented in different
ways. To provide context for understanding the need for these operators, we
want to examine two behaviors of a LINQ query: deferred query evaluation and
extension methods resolution. Both of these concepts are important for all LINQ
implementations, so read the following sections carefully.
Deferred Query Evaluation
A LINQ query is not evaluated when it is defined but when it
is used. Consider the following query definition:
This code declares a very simple query that contains just two
items, as shown by the result of the following line of code:
Now we want to change the content of the source sequence by adding
a new customer:
If you enumerate the expr variable again or
just check its item count, you will find a result different than before, as you
can see when executing the code in Listing
4-55.
Listing 4-55: Example
that shows when expression trees are evaluated
Console.WriteLine("\nItems after source sequence modification: {0}", expr.Count());
foreach (var item in expr) {
Console.WriteLine(item);
}
The result of the code in Listing
4-55 looks like the following. The new customer, Roberto, is included
in the result, even though it has been added after the expr
query definition:
|
Important |
From a logical point of view, a LINQ query describes a kind
of “query plan” because it is not executed until it is used and will be
executed again and again, every time you run it. Some LINQ implementations-such
as LINQ to Objects-implement this behavior through delegates, while others-such
as LINQ to SQL-might use expression trees. We call this
behavior “deferred query evaluation,” and it is a fundamental concept in LINQ,
regardless of its implementation.
|
Deferred query evaluation is useful because you can define
queries once and apply them several times. Regardless of whether the source
sequence has been changed, the result will always be updated to the last
sequence content. However, consider a situation in which you want a snapshot of
the result at a particular “safe point” to use many times, even if the source
sequence changes in the meantime. You need to make a copy of the result, and
conversion operators will help you do that.
Extension Methods Resolution
Extension methods resolution is one of the most important
concepts to understand if you want to master LINQ. Consider the following code.
In it, we define a custom list of type Customer, called
Customers, and a class, CustomersEnumerable,
that provides an extension method, called Where, which
applies specifically to instances of the Customers type:
If we use our usual customers array, the
behavior of the query in Listing 4-56
is quite interesting.
Listing 4-56: A
query expression over a custom list of type Customers
Customers customersList = new Customers(customers);
var expr =
from c in customersList
where c.City == "Brescia"
select c;
foreach (var item in expr) {
Console.WriteLine(item);
}
The query expression will be converted by the compiler into the
following code, as we saw early in this article:
As a result of the presence of the CustomersEnumerable
class, the extension method Where will be the one
defined by CustomersEnumerable, instead of the
general-purpose one defined in System.Linq.Enumerable.
(To be considered as an extension method container class, the CustomersSequence
class must be in the current namespace or in any namespace included in active
using directives.) Now we are experiencing the real power of LINQ.
Using extension methods, we are able to define custom behaviors for specific
types. In the following chapters, we will discuss LINQ to SQL, LINQ to XML, and
other implementations of LINQ. These implementations are just specific
implementations of query operators, thanks to the extension methods resolution
realized by the compilers.
At this time, everything looks fine, and it is, of course! By
the way, imagine that you need to query the custom list of type
Customers with the standard Where extension
method rather than with the specialized one. You should convert the custom list
to a more generalized one to divert the extension method resolution made by the
compiler. This is another scenario that can benefit from conversion operators.
|
|
|
|