Welcome to MSDN Blogs Sign in | Join | Help

Mitsu's blog

Discussing topics related to .Net, WPF, C# and Linq
Linq: how to share parameters between lambda expressions ?

Before going into Linq, here is again one of my pictures: Le Louvre by night, Paris

image

 

When using Linq to objects, you will quickly feel the need to pass some parameters from a method to another but it’s not so easy because each Linq method is not calling the following one. In a Linq sequence, each method is using the result computed by the previous one. So, local contexts are not visible from one method to another.
The compiler is using two technical different ways to let parameters go out of a method.

As an example, let’s first see how the .SelectMany() method is working.

var values1 = new string[] { "1", "2" };
var values2 = new string[] { "A", "B", "C" };
var q =
   
from s1 in values1
   
from s2 in values2
   
select s1 + s2;

image

This very little example shows that s1 and s2 are both accessible in the select. It’s nice, but how does this work ?
You must know that the ‘from’ statement of the Linq sugar syntax does not match any existing Linq method. Let’s see how we would have written this using the classical C# syntax.

var values1 = new string[] { "1", "2" };
var values2 = new string[] { "A", "B", "C" };
var q =
   
values1.SelectMany(s1 => values2.Select(s2 => s1 + s2));

Let’s focus on the SelectMany parameter:

SelectMany(Func<TSource, IEnumerable<TResult>> selector).

The method must return an IEnumerable<TResult>. In our example, we get it using values2.Select(). We are now touching the interesting point. The parameter of the .Select() is also a lambda “s2 => s1 + s2”. In Linq to object a lamda generates an anonymous method and anonymous methods can access their host scope. Here, s1 is visible inside values2.Select().

So the first solution to share parameters between methods is to nest them

like “.SelectMany(s => s.Select(s2 =>’s in accesible here’ ))”

instead of creating a sequence “source.Skip(‘scope1’).Take(‘scope2’)”

Now what if we would like to share a parameter between two following methods like Skip() and Take() ?

Let’s see how the “let” keyword works.

In the next example, I will start from a list of numbers stored as strings.
I would like to get only numbers greater than 10 and order them by their value but keeping the result as an enumeration of string.

We can write it quickly this way:

var values = new string[] { "12", "4", "7", "18", "32" };
var q =
   
from s in values
   
where Convert.ToInt32(s) > 10
   
orderby Convert.ToInt32(s)
   
select s;

This works fine but we all notice the ugly “Convert.ToInt32(s)” used twice on the same value. Thanks to the “let” keyword we can create a new parameter that will be accessible in all the query.

var values = new string[] { "12", "4", "7", "18", "32" };
var q =
   
from s in values
   
let i = Convert.ToInt32(s)
   
where i > 10
   
orderby i
   
select s;

We can see that the “let” keyword solves this problem extremely easily. But once again the sugar syntax of Linq is really magic. As we always want to know the secrets of magic tricks, let’s try to get the same result without using the “let” keyword and we will find out what the C# compiler is really doing.

Let’s start by decomposing all the steps using the regular C# syntax.

var step1 = values.Where(s => Convert.ToInt32(s) > 10);
var step2 = step1.OrderBy(s => Convert.ToInt32(s));

I have chosen to decompose this way to show that the only place to share something between the Where() and the OrderBy() is the result.

Now we have to make a new parameter travel across the sequence in addition to the current result. The idea is to create a new type to group the current result and the new value. To achieve this easily we can just use a anonymous type.

//step0: Create an anonymous type grouping the original string and the new value in a single element 
//step1, step2: 'i' is now accessible as a property of each element 
//step3: Get back to an enumeration of string by removing the temporary anonymous element 

var step0 = values.Select(s => new { s = s, i = Convert.ToInt32(s) }); 
var step1 = step0.Where(tmp => tmp.i > 10); 
var step2 = step1.OrderBy(tmp => tmp.i); 
var step3 = step2.Select(tmp => tmp.s); 

We can of course write this in a single Linq query

var q = 
   
from tmp in 
       
(from s in values 
        
select new { S = s, I = Convert.ToInt32(s) }) 
   
where tmp.I > 10 
   
orderby tmp.I  
   
select tmp.S;

which is the exact equivalent of what is compiled when using the ‘let’ keyword

var q =
   
from s in values
   
let i = Convert.ToInt32(s)
   
where i > 10
   
orderby i
   
select s;

Of course the Linq syntax is extremely short but it’s always nice to know what’s happening behind the scene.

I hope this sample can also help you to solve some other problems when playing with Linq.

Posted: Monday, May 18, 2009 9:38 PM by mitsu
Filed under: , ,

Comments

Fabien Lavocat said:

That a very good article! Thanks a lot. Before reading it, I didn't know .SelectMany method

# June 17, 2009 3:37 PM

Christian Schiffer said:

Nice posting and nice picture.

Off topic: would you post a larger version of that picture? Prefably full hd (1920*1080 for instance, I so much would like it as my desktop background :)

# August 4, 2009 1:20 PM

mitsu said:

@Christian: you can now click on the picture and jump to the original one. I love this picture too ! :p

# August 12, 2009 8:42 AM

Henri said:

This is the first really good explanation i see for how 'let' works and what it is used for. Thanks very much.

# August 26, 2009 5:29 AM

Owen Emlen said:

How come not orderby tmp.I?  Or is it a typo?

var q =  

   from tmp in  

       (from s in values  

        select new { S = s, I = Convert.ToInt32(s) })  

   where tmp.I > 10  

   orderby tmp.S  

   select tmp.S;

# August 26, 2009 7:52 PM

NonGT said:

Nice article! It does clearly explain me what exactly the "let" keyword.

Thank you! :)

# August 26, 2009 8:30 PM

mitsu said:

Thanks for your comments.

@Owen: yes, typo mistake. thanks

# August 27, 2009 5:36 AM

BMavi said:

Thanks Mitsu, Good examples for understanding 'let' and 'SelectMany', never used before. Really appreciate your efforts.

# August 28, 2009 3:38 PM

Arun George John said:

Nice article. Never used the "let" keyword before, but this made it easy to understand!

Thanks!

# August 31, 2009 1:49 AM

Lou Gutnicki said:

Excellent article.

Clearly written.

Thanks

# August 31, 2009 2:30 PM

Kulveer said:

Nice article about Let and SelectMany.

Thanks

# August 31, 2009 5:23 PM

Kulveer said:

# August 31, 2009 5:25 PM

aganya said:

very nice article :-)

# September 4, 2009 7:56 AM

Pravat Maskey said:

Nice job dude, I know let now :)

# September 10, 2009 5:41 AM

Jignesh said:

Good artical man, thanx to heighlity 'let' and 'SelectMany', many of us never used them before.

# September 18, 2009 3:22 AM

SoCal said:

Great article! You explain a lot of stuff in a short article. Thanks.

# September 21, 2009 5:13 PM

HaoLT said:

I have a dificult mather that using LinQ.

Usually, Linq is used with object is a pointer set [] or <template> type. I have a array, must i have convert to these type above?

# September 23, 2009 12:38 PM

mitsu said:

Not sure to understand..

Linq always starts with a typed enumeration (generic). Arrays are typed enumerations, so you can start a Linq query from "values" if "values" is an int[] for example.

# September 23, 2009 5:34 PM

HaoLT said:

Let me add more infomation:

// Want to select some items with "Where" statement

           Array test1; // Can't use "Where" statement

           test1. Where (item => item = 2); // incorrect syntact

           int[] test2; // Can use "Where" statement

           test2.Where(item => item = 2); // correct syntact

           //To using "Where" statement, must i convert from array to [] int

# September 24, 2009 1:03 PM

mitsu said:

If test1 contains integers, yes you have to convert it. As I said the enumeration must be typed.

You can 'type' your array without generating a new one. You can just write: test1.Cast<int>().Where(...)

# September 24, 2009 4:35 PM

HaoLT said:

I have just begin with LinQ. Thank about your guide.

I think it readly help me.

# September 25, 2009 11:30 AM

Bala said:

Very Nice article,Excellent one!!

Thanks..

# October 2, 2009 8:57 AM

François Vallernaud said:

Before reading your article i was using KeyValuePair to do the same thing

.Select(s => new KeyValuePair<string,int>(s, Convert.ToInt32(s)))

.OrderBy(kvp => kvp.Value)

....

I agree that your solution is much more confortable

# October 23, 2009 5:14 AM

mitsu said:

Agree with you François. Just keep in mind that the let keyword can only be used in the sql-like syntax of Linq.

# October 23, 2009 5:28 AM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

  
Enter Code Here: Required

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Page view tracker