LINQ is almost miraculous in its ability to query many types of data. How could one tool do so much? In this post, we're going to discover that there's nothing mysterious about it. It's just an example of good software design.
In the previous post of this series, we saw this example of LINQ. If you wish, you can download the full solution here and bring up this code in Program.cs in the Demo1 project.
static IEnumerable<City> GetLargest_LinqMethod(int numDesired)
{
return Cities
.OrderByDescending(c => c.Population)
.Take(numDesired);
}
If you were to use Visual Studio or MSDN to go to the definition of OrderByDescending, you'd find that it's an extension method on the IEnumerable interface, with the following signature. I've added some comments and line-breaks for clarity.
// It returns an ordered enumerable, which
// in our case will be another list of City objects.
public static IOrderedEnumerable<TSource>
// It's generic on TSource (cities)
// and TKey, which I'll explain below.
OrderByDescending<TSource, TKey>
// It extends IEnumerable.
(this IEnumerable<TSource> source,
// It expects a delegate whose input parameter
// is a TSource (City) and that returns the
// value we'll order on.
Func<TSource, TKey> keySelector);
The Return Type
What makes the method syntax of LINQ flow so nicely is that each method returns the appropriate type of object for the next. (This buzzword for this is "fluent interface.") Thus, OrderByDescending returns an IOrderedEnumerable<TSource>, which is derived from IEnumerable<TSource>. "Take" is another extension method defined on IEnumerable<TSource>, so it is able to consume the output of OrderByDescending.
The Parameter
OrderByDescending takes a parameter of type Func<TSource,TKey>. The "key" here is the value that OrderByDescending will order by. If you're not familiar with the Func<> delegate, it's a very convenient way of specifying a general-purpose delegate that takes one or more parameters (just a City in our case) and returns a result (Population). We specify the Func using a Lambda expression that returns the Population propert of a City object.
Summary
There's really nothing special about the innards of LINQ. It's typically implemented as a garden-variety set of extension methods on a data source (such as IEnumerable<T>). The magic comes from the fluent interface. One extension method passes its results to the next so you can chain them together in very natural queries.
If we had been using the query format of LINQ, introduced in the previous post, we would have seen more magic. That format does introduce a new syntax for invoking the extension methods, but under the covers it's really doing what we've seen here.
In the next post, we'll discuss an important concept in LINQ: deferred execution.