Observant readers may have noted that the standard Where operator is implemented using the yield construct introduced in C# 2.0. This implementation technique is common for all the standard operators that return sequences of values. The use of yield has an interesting benefit which is that the query is not actually evaluated until it is iterated over, either with a foreach statement or by manually using the underlying GetEnumerator and MoveNext methods. This deferred evaluation allows queries to be kept as IEnumerable<T>-based values that can be evaluated multiple times, each time yielding potentially different results.
For many applications, this is exactly the behavior that is desired. For applications that want to cache the results of query evaluation, two operators, ToList and ToArray, are provided that force the immediate evaluation of the query and return either a List<T> or an array containing the results of the query evaluation.
To see how deferred query evaluation works consider this program that runs a simple query over an array:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
// declare a variable that represents a query
IEnumerable<string> ayes = names.Where(s => s[0] == 'A');
// evaluate the query
foreach (string item in ayes)
Console.WriteLine(item);
// modify the original information source
names[0] = "Bob";
// evaluate the query again, this time no "Allen"
foreach (string item in ayes)
Console.WriteLine(item);
The query is evaluated each time the variable ayes is iterated over. To indicate that a cached copy of the results is needed, we can simply append a ToList or ToArray operator to the query like this:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();
// iterate over the cached query results
foreach (string item in ayes)
Console.WriteLine(item);
// modifying the original source has no effect on ayes
names[0] = "Bob";
// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
Console.WriteLine(item);
Both ToArray and ToList force immediate query evaluation. The same is true for the standard query operators that return singleton values (for example: First, ElementAt, Sum, Average, All, Any).
The IQueryable<T> Interface
The same deferred execution model is usually desired for data sources that implement the query functionality by using expression trees, such as LINQ to SQL. These data sources can benefit from implementing the IQueryable<T> interface for which all the query operators required by the LINQ pattern are implemented by using expression trees. Each IQueryable<T> has a representation of "the code needed to run the query" in the form of an expression tree. All the deferred query operators return a new IQueryable<T> that augments that expression tree with a representation of a call to that query operator. Thus, when it becomes time to evaluate the query, typically because the IQueryable<T> is enumerated, the data source can process the expression tree representing the whole query in one batch. As an example, a complicated LINQ to SQL query obtained by numerous calls to query operators may result in only a single SQL query getting sent to the database.
The benefit for data source implementers of reusing this deferring functionality by implementing the IQueryable<T> interface is obvious. To the clients who write the queries, on the other hand, it is a great advantage to have a common type for remote information sources. Not only does it allow them to write polymorphic queries that can be used against different sources of data, but it also opens up the possibility for writing queries that go across domains.