The LINQ Principle
Written by Mike James   
Thursday, 25 June 2020
Article Index
The LINQ Principle
IEnumerable
Generic enumeration
Raw LINQ
More with LINQ

 

You should find this pleasing and exciting. The LINQ extension functions have been added to a class that you have created that simply implements IEnumberable. You not have not just a collection of data but a collection that you can query and manipulate in quite complex ways.

Of course there are many facilities in C# that can make using the Where query 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 as 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 LINQ example  you will find in the documentation 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 is 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. This idea deserves a deeper explanation.

Custom select

The nature of the Select depends on the nature of the data item.

For example, if the item takes the form of a row of a table then a select can pick out individual columns.

If the data item is an array then it can select individual elements.

You can see that selecting part of the data item could be a complex operation.  It helps greatly to know how LINQ works in terms of extension methods in understanding what it does and how to make use of it. For example the definition of one overlay of the Select extension method is:

public static IEnumerable<TResult>
  Select<TSource, TResult>(
     this IEnumerable<TSource> source,
      Func<TSource, TResult> selector)

You can now clearly see that you have to specify a source type and a result type and a function that does the conversion between the two types.

When you write a C# query expression using Select all you do is specify the transformation function and let the compiler work out the types of the source and result data. For example,

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

The final select now creates a function:

Func<int, int> MySelect=i->i*2;

and passes, as a lambda expression, in the call to the Select method which is:

var q = col.
Where<int>(i => i > 250).
Select<int,int>(i=>i*2);

A little thought reveals that this mechanism is more sophisticated than it looks.

For example, you can call methods defined in the source data type to perform more complicated data transformation. For example,

var q2 = col.
Where<int>(i => i > 250).
Select<int,string>(i=>i.ToString());
foreach (string o in q2)
{
MessageBox.Show(o);
}

Of course you can add custom methods to the class that do much more by way of conversion and manipulation.

 



Last Updated ( Thursday, 25 June 2020 )