LINQ to Objects for the .NET developer

Page 2 of 3
  1. Introduction
  2. LINQ and extension methods
  3. Custom select

LINQ and extension methods

This article was originally published in VSJ, which is now part of Developer Fusion.

Now we have a simple generic IEnumerable class we can start to use LINQ with it – yes it really is this simple. What the LINQ system does it to add extension methods to the IEnumerable generic interface – I’ll come back to what an extension method is after we have seen them in action. For the moment just accept the fact that there are a large number of additional methods available to any class that implemented the IEnumerable generic interface, and most of these return an IEnumerable object – which is a more important observation than you might think.

To get started let’s look at the Where extension method. If you look at its definition you will discover that it’s:

public static IEnumerable<T> Where<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate
)

Ignore the “this” for the moment because it’s part of every extension method. The first parameter, source, is an IEnumerable that will be “scanned” for all of the entities that the predicate returns true for. The predicate is just a generic delegate that “wraps” a function that accepts a single input parameter of the first specified type and returns a result of the second specified type. In this case predicate can be seen to accept a single parameter of type T and return a Boolean result. If we are going to use Where the first thing we need is a suitable predicate. This can be created in many ways, but to keep the explanation simple let’s do it the old-fashioned but rather long-winded way of first defining a suitable function and then wrapping it in a delegate. First define the function:

bool MyTest(int i)
{
    return i > 250;
}

This simply tests to see if the value is greater than 25 and returns true if so and false otherwise. To make use of this we have to first wrap it in a delegate:

Func<int, bool> MyDelegate = new Func<int, bool>(MyTest);

Next we create the data collection as before:

TestCollection col = new TestCollection(5);

And finally use the Where method with the delegate we have defined:

IEnumerable<int> q = col.Where<int>(MyDelegate);

If you try this out nothing happens. No really nothing happens… the operation of enumerating and extracting the entities smaller than 250 is only performed when it is required. In practice it is triggered by calling the generic GetEnumerator method, usually within a foreach loop. For example to see the results of the query:

foreach (int o in q)
{
    MessageBox.Show(o.ToString());
}

To emphasise – the results of the query are only stored in q when we start the foreach loop.

Of course there are many facilities in C# that can make using Where much simpler, but this is LINQ in the raw, and it illustrates exactly how it all works. I think it also makes it clear how clever the idea is and demonstrates that LINQ isn’t just SQL added to C#.

There are lots of other extension methods and as they all extend IEnumerator and as they all return an object of type IEnumerator you can simply chain them together. For example, to apply a second Where condition to the same query you would write something like:

IEnumerable<int> q = col.
    Where<int>(MyDelegate1).
    Where<int>(MyDelegate2);

If you lookup the range of extension methods provided you will see that it’s very easy to build up complicated queries, and it’s also easy to add your own extension methods to make LINQ capable of whatever query you want it to. Syntactic sugar So far the LINQ query example doesn’t look a great deal like any other example you will find and the reason is that it doesn’t use any of the syntactic sugar and other facilities introduced to make LINQ look more like SQL and to make it generally easier to use. So the time has come to package it all up.

The first simplification is that we can use a lambda expression to create the predicate method. This makes it possible to do away with the separate function and write something that looks like a condition as a parameter to most of the extension methods. If you recall a lambda expression is a function, without a name, that returns a single value and can be assigned directly to a delegate. For example:

Func<int, bool> MyDelegate2 = i => i > 250;

…creates a delegate that can be used in the call to Where just as in the previous example. However, as the Where method has a parameter of type Func(int,bool), we can do the job directly without the need to create an explicit delegate:

IEnumerable<int> q = col.Where<int>(i => i > 250);

With this simple change the query is beginning to look a lot more like SQL. However we can go one better. It’s up to each .NET language to choose to provide a linguistic wrapper for the LINQ system and they can do it however they like. In practice there is a lot of sense in using something close to a dialect of SQL. C# and VB both wrap LINQ with SQL-like instructions. For example, our simple query can be written:

IEnumerable<int> q = from i in col
    where i > 250
    select i;

This is compiled to the same set of method calls given earlier and is completely equivalent. You can even make it slightly simpler by allowing the compiler to work out the type of q from the return result of the method calls, i.e. use an anonymous type:

var q = from i in col where i > 250
    select i;

Now you can see why these new features were introduced to C#.

In general a LINQ query expression always starts with:

from variable in IEnumerable object

This specifies the object to be used in the query and the parameter to be passed to all of the methods, the “range” variable. Then there’s a wide choice of possible clauses which correspond to each of the extension methods and when used cause them to be called with the parameters and predicates specified. Finally every LINQ query expression has to end with a Select. The Select is easy to use but often not so easy to understand how it is implemented. The idea is that Select performs a projection or transformation on the basic data item used in the query – that is it takes a data type and projects it to a “smaller” subtype.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The generation of random numbers is too important to be left to chance.” - Robert R. Coveyou