Deferred Query Evaluation and Extension Methods Resolution


Conversion operators (which will be described in the last section of this chapter) 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:

 List<Customer> customersList = new List<Customer>(customers); var expr =     from   c in customersList     where  c.Country == Countries.Italy     select c;

This code declares a very simple query that contains just two items, as shown by the result of the following line of code:

 Console.WriteLine("\nItems after query definition: {0}", expr.Count());

Now we want to change the content of the source sequence by adding a new customer:

 customersList.Add(   new Customer {Name = "Roberto", City = "Firenze",     Country = Countries.Italy, Orders = new Order[] {     new Order {Quantity = 3, IdProduct = 1 , Shipped = false,       Month = "March"}}});

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

image from book
  Console.WriteLine("\nItems after source sequence modification: {0}", expr.Count()); foreach (var item in expr) {     Console.WriteLine(item); } 
image from book

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:

 Items after source sequence modification: 3 Paolo - Brescia – Italy Marco - Torino – Italy Roberto - Firenze - Italy

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. (See Chapter 2 for a definition of an expression tree.) 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:

 public sealed class Customers: List<Customer> {     public Customers(IEnumerable<Customer> items): base(items) {} } public static class CustomersEnumerable {     public static IEnumerable<Customer> Where(         this Customers source, Func<Customer, bool> predicate) { ... }     public static IEnumerable<Customer> Where(         this Customers source, Func<Customer, int, bool> predicate) { ... } }

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

image from book
  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); } 
image from book

The query expression will be converted by the compiler into the following code, as we saw early in this chapter:

 var expr =     customersList     .Where(c => c.City == "Brescia")     .Select(c => c);

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.




Introducing Microsoft LINQ
Introducing MicrosoftВ® LINQ
ISBN: 0735623910
EAN: 2147483647
Year: 2007
Pages: 78

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net