codethinked (kōdthĭngked) adj. To be consumed by or obsessed with code.

The Linq "let" keyword

I've been busy working on some presentations that I have coming up, and so I haven't had too much time to update, but I was messing around with Linq and I found myself using the "let" keyword quite a bit to make some of my queries more readable. So I decided to do a small write-up so that you can see the joy of using the "let" keyword.

Lets say we have a small set of data that looks like this:

var nameList = new List<string>
                   {
                       "Matt",
                       "Adam",
                       "John",
                       "Peter",
                       "Owen",
                       "Steve",
                       "Richard",
                       "Chris"
                   };

And we have a method where we need to return a list of names that start or end with vowels and are either 4 or 5 characters long. So, our inexperienced Linq developer quickly codes up a query that looks like this (yes, I know the "ToUpper" is ugly, but I'm not changing it now :) ):

var vowels = new List<string> {"A", "E", "I", "O", "U"};
var names = (from p in nameList                                                  
            where
            (vowels.Any(v => p.ToUpper().StartsWith(v)) 
            || vowels.Any(v => p.ToUpper().EndsWith(v))) &&
            (p.Length == 4 
            || p.Length == 5)
            select p).ToList();

First we have to define our List of vowels, since we are using it twice. Then we code up our query just like we would if we were writing a sql statement. The only problem is that by just looking at this query it is pretty hard to tell what it is doing. But what if we had a way to break up the query so that we could make its purpose more clear? Well, thankfully we do!

var names = (from p in nameList
            let vowels = new List<string> { "A", "E", "I", "O", "U" }
            let startsWithVowel = vowels.Any(v => p.ToUpper().StartsWith(v))
            let endsWithVowel = vowels.Any(v => p.ToUpper().EndsWith(v))
            let fourCharactersLong = p.Length == 4
            let fiveCharactersLong = p.Length == 5
            where
            (startsWithVowel || endsWithVowel) &&
            (fourCharactersLong || fiveCharactersLong)
            select p).ToList();

Now, doesn't that look better? We define four intermediate variables that hold our vowels and booleans for our tests, then in the where clause we just check our boolean values. The result is a where clause that is very readable. There are tons more uses for "let", but I hope that you start using it in your queries to make them more readable.

Comments

S M Sohan

I liked the article and think its worth. Thanks for putting your efforts on this. However, I didnt see the name of my country 'Bangladesh' when I wanted to select it from the list of countries in the comment form :-(

S M Sohan

April 15. 2008 03:07

Frans Bouma

The 'let' keyword in database targeting linq queries causes a subquery to be produced. So the more let statements you use, the more subqueries you'll get.

Frans Bouma

April 15. 2008 06:26

Netherlands
Justin Etheredge

@S M Sohan Sorry, it looks like BlogEngine.net doesn't include that country in the software.

@Frans Excellent info to be aware of, thanks!

Justin Etheredge

April 15. 2008 11:11

United States
Judah

Ah, thanks Justin, that helps clear things up. I was never quite certain of the purpose of the the let keyword. This helps. Thanks man.

Judah

April 15. 2008 15:30

United States
pls

There is no need to use lambda in:
  vowels.Any(v => p.ToUpper().StartsWith(v))
We can simply pass delegate:
  vowels.Any(p.ToUpper().StartsWith)

After add few changes to this example, I think now it is more readable:
  var names = (from p in nameList
         let vowels = new List<char> { 'A', 'E', 'I', 'O', 'U' }
         let pToUpper = p.ToUpper()
         let shouldContainVowel = new List<char> { pToUpper[0], pToUpper[p.Length - 1] }
         let allowedLength = new List<int> { 4, 5 }
         where
         shouldContainVowel.Any(vowels.Contains) &&
         allowedLength.Any(l => p.Length == l)
         select p).ToList();

pls

April 15. 2008 20:26

Poland
Egil

Is there a performance penalty for using the "let" keyword?

Egil

April 16. 2008 12:01

Denmark
Skup

Just be careful about a few things...
first, the let statements are evaluated once for each item...
so you build a new List<char> for each name in your nameList, and a new allowedLength List<int>.
perhaps this two can be moved to local variables outside of the linq query. The other let statements are ok since they new to be reevaluated for each item.

Then, the let statement is translated to a .Select( p => new {p = p, pToUpper = p.ToUpper()} for instance. where p is the current item. Thus, you change a local variable in your where expression to a chained expression in your query.
This is good when you need several times the evaluated value since its evaluated only once, the just taken from the iterated anonymous object containing the value... but it can be a weight when not needed. You choose.

Skup

April 16. 2008 12:02

France
Justin Etheredge

@pls Looks good, thanks!

@Egil I believe that Skup answered your question.

@Skup Yes, I know they are evaluated once for each. I was using "let" statements a bit too liberally to show its use. As far as the last part of your comment, I could be missing something, but I am not following you.

Justin Etheredge

April 16. 2008 13:43

United States
Skup

Sure, it's not very easy to explane.

When linq expression are converted to method call (generally extension methods), the let statement is converted to a Select that returns an anonymous object containing the original item and the values computed in the let expressions.
This way, the next method can access both the original item and the computed values.

In your sample, the .Where method is executed on an enumeration of anonymous object containing { p, vowels, pToUpper,
shouldContainVowel, allowedLength }.

Hope it's a bit clearer...

Skup

April 17. 2008 07:31

France
Justin Etheredge

@Skup Thanks, that clears it up. I'll have to explore it a bit deeper when I get time. I'll be sure to put up a post about the way that the "let" keyword works using yours and Frans' comments.

Justin Etheredge

April 17. 2008 10:13

United States
pingback

Pingback from code-inside.de

Weekly Links: ASP.NET MVC, Silverlight 2, WPF, WCF… | Code-Inside Blog International

code-inside.de

April 21. 2008 18:19

pingback

Pingback from johanleino.wordpress.com

Propagating Updates to Content Types « Johan Leino

johanleino.wordpress.com

August 11. 2009 16:07

Add Comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading