Somewhat surprisingly, adding query expressions4 required no changes to the Common Language Runtime (CLR) or to the Common Intermediate Language (CIL). Rather, the C# compiler simply translates query expressions into a series of method calls. Consider, for example, the query expression from Listing 16.1, a portion of which appears in Listing 16.16.
After compilation, the expression from Listing 16.16 is converted to an IEnumerable<T> extension method call from System.Linq.Enumerable, as shown in Listing 16.17.
As discussed in Chapter 15, the lambda expression is then itself translated by the compiler to emit a method with the body of the lambda, and its usage entails allocation of a delegate to that method.
Every query expression can (and must) be translated into method calls, but not every sequence of method calls has a corresponding query expression. For example, there is no query expression equivalent for the extension method TakeWhile<T>(Func<T, bool> predicate), which repeatedly returns items from the collection as long as the predicate returns true.
For those queries that do have both a method call form and a query expression form, which is better? This is a judgment call. Some queries are better suited for query expressions, whereas others are more readable as method invocations.
This chapter introduced a new syntax—namely, query expressions. Readers familiar with SQL will immediately see the similarities between query expressions and SQL. However, query expressions also introduce additional functionality, such as grouping into a hierarchical set of new objects, which is unavailable with SQL. All of the functionality of query expressions was already available via standard query operators, but query expressions frequently provide a simpler syntax for expressing such a query. Whether standard query operators or query expression syntax is used, however, the end result is a significant improvement in the way developers can code against collection APIs—an improvement that ultimately provides a paradigm shift in the way object-oriented languages are able to interface with relational databases.
In the next chapter, we continue our discussion of collections by investigating some of the .NET framework collection types and exploring how to define custom collections.