Introduction to LINQ: Part IV - Putting it All Together
This is part four of my introduction to LINQ tutorial. If you have not read the previous parts in this series and you do not have a decent understanding of delegates, lambdas, and extension methods then please go back and check those out first.
Table of Contents
In this final section it is time to put everything together. LINQ is a culmination of the previous three sections. In keeping with tradition I will start off with a quote from the MSDN:
.NET Integrated Query defines a set of general purpose standard query operators that allow traversal, filter, and projection operations to be expressed in a direct yet declarative way in any .NET-based programming language. The standard query operators allow queries to be applied to any IEnumerable-based information source. LINQ allows third parties to augment the set of standard query operators with new domain-specific operators that are appropriate for the target domain or technology. More importantly, third parties are also free to replace the standard query operators with their own implementations that provide additional services such as remote evaluation, query translation, optimization, and so on. By adhering to the conventions of the LINQ pattern, such implementations enjoy the same language integration and tool support as the standard query operators.
Again this is a pretty cryptic description. The easiest way for me to introduce you to LINQ as a whole is by re-introducing you to one of our previous projects in this tutorial, the sci-fi show filter application. Here is the code we had written in our sample of how to use lambda expressions:
class Program
{
static void Main(string[] args)
{
// Create a list of sci-fi shows.
List<string> scifiShows = new List<string>
{
"Star Trek: The Next Generation",
"Star Trek: Voyager",
"Star Trek: Deep Space Nine",
"Battlestar Gallactica",
"Doctor Who",
"Stargate SG-1",
"Stargate Atlantis",
"Stargate Universe",
"Futurama"
};
// Get a filtered list of shows by calling FilterList() and passing in our list of shows and a
// lambda expression to be used when checking the values in the list.
List<string> filteredList = FilterList(scifiShows, x => x.StartsWith("Star Trek"));
// Print each show in the filtered list to the console.
foreach (string show in filteredList)
{
Console.WriteLine(show);
}
Console.ReadLine();
}
/// <summary>
/// Filters a string list based on a delegate function used to check each value in the list.
/// </summary>
/// <param name="list">The list to be filtered.</param>
/// <param name="evaluateCondition">The delegate to be invoked when checking each value.</param>
/// <returns>The filtered string list.</returns>
static List<string> FilterList(List<string> list, Func<string, bool> evaluateCondition)
{
List<string> filteredList = new List<string>();
foreach (string show in list)
if (evaluateCondition(show))
filteredList.Add(show);
return filteredList;
}
}
We are going to make one rather minor change to this application that will hopefully bring everything full-circle for you. The first thing to change is the FilterList
method. We are going to turn it into an extension method. Put it in its own class and make it public like so:
public static class ExtensionMethods
{
/// <summary>
/// Filters a string list based on a delegate function used to check each value in the list.
/// </summary>
/// <param name="list">The list to be filtered.</param>
/// <param name="evaluateCondition">The delegate to be invoked when checking each value.</param>
/// <returns>The filtered string list.</returns>
public static List<string> FilterList(this List<string> list, Func<string, bool> evaluateCondition)
{
List<string> filteredList = new List<string>();
foreach (string show in list)
if (evaluateCondition(show))
filteredList.Add(show);
return filteredList;
}
}
Now, modify the code that calls FilterList
accordingly:
static void Main(string[] args)
{
// Create a list of sci-fi shows.
List<string> scifiShows = new List<string>
{
"Star Trek: The Next Generation",
"Star Trek: Voyager",
"Star Trek: Deep Space Nine",
"Battlestar Gallactica",
"Doctor Who",
"Stargate SG-1",
"Stargate Atlantis",
"Stargate Universe",
"Futurama"
};
// Get a filtered list of shows by calling FilterList() extension method and passing in our
// list of shows and a lambda expression to be used when checking the values in the list.
List<string> filteredList = scifiShows.FilterList(x => x.StartsWith("Star Trek"));
// Print each show in the filtered list to the console.
foreach (string show in filteredList)
{
Console.WriteLine(show);
}
Console.ReadLine();
}
That's it! You're done. You just created your first LINQ-style extension method. I'll get to some more specifics about LINQ momentarily but for now you should be excited. You haven't even gotten into the meat of actual LINQ extensions and you already have a somewhat intimate understanding of its inner-workings. Let's quickly step through what we did here.
- We extended a collection of type
List<string>
with a method calledFilterList
. - We then gave the extension method a dependency on a delegate:
Func<string, bool>
. - Finally we built our list, called our extension method, and passed in a lambda expression (anonymous method) to be executed by the extension method when checking values in the list.
That is all there is to it. You could use the FilterList
extension on ANY List<string>
you ever instantiate just so long as you have a reference to the namespace where your extension method class was defined.
System.Linq
Now that you know how to build a lambda powered extension method you can begin to understand Language Integrated Queries. Everything related to LINQ lives in the System.Linq
namespace. At its core LINQ is merely a library of extension methods that extend the types IEnumerable
and IEnumerable<T>
. It doesn't seem like much until you put them to use and suddenly realize their power.
There are far too many LINQ extensions to get into in this introductory tutorial, but there are some very common ones that you should be aware of. Probably the most common extension is the .Where()
method. .Where()
allows you to filter collections in much the same way we did with our list of shows. In fact, we can shrink the code in that application by a fair amount if we were to take advantage of existing LINQ functions instead of building our own filtering extension. Modify your code to look like this:
class Program
{
static void Main(string[] args)
{
// Create a list of sci-fi shows.
List<string> scifiShows = new List<string>
{
"Star Trek: The Next Generation",
"Star Trek: Voyager",
"Star Trek: Deep Space Nine",
"Battlestar Gallactica",
"Doctor Who",
"Stargate SG-1",
"Stargate Atlantis",
"Stargate Universe",
"Futurama"
};
// Get a filtered list of shows by using LINQ.
List<string> filteredList = scifiShows.Where(x => x.StartsWith("Star Trek")).ToList();
// Print each show in the filtered list to the console.
foreach (string show in filteredList)
{
Console.WriteLine(show);
}
Console.ReadLine();
}
}
What's the difference here? Not much. The .Where()
extension operates a lot like our custom FilterList
extension did. The only real difference is that it returns IEnumerable<T>
. Because of this the compiler throws an error when trying to assign the results of the extension method to our List<string>
. Fortunately, LINQ has made it easy to transition back and forth between plain old IEnumerable
and List
through the use of .ToList()
. Likewise, you can turn lists into plain enumerables using .AsEnumerable()
.
The power of LINQ is endless and can be extended to your heart's content. One can see why it is called a language integrated query. The ability to filter down and sift through collections in a strongly-typed way is amazingly useful. These techniques especially become useful when you learn about Object Relational Mappers (ORMs) like LINQ to SQL and Entity Framework, but those topics are out of the scope of this tutorial.
In relation to ORM there is another side to LINQ that you should eventually learn about called "expressions". Like I said, this is out of scope here, but I just wanted to mention it so that you know what to look for next in your journey through the world of LINQ.
I hope this tutorial series helped you gain a better understanding of LINQ and all the concepts that build its foundation. Be sure to check back here for more; I'm thinking a discussion of ORMs and expressions are in my near future :)