Digging into the Linq "let" keyword

In my last post I talked abou the Linq “let” keyword and showed you how it could be used to simplify linq queries. Well, this post was thrown up pretty quick and I didn’t get to research the topic in the way that I normally do, and I quickly got called out by a few readers who pointed out that there are some implications with using the “let” keyword that may not be initially apparent.

In the last post I used a query that looked like this:

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();

In this case the “let” keyword greatly simplifies what would have been an otherwise very ugly “where” clause. So, I started thinking, how can I show what happens when this query is turned into an Expression object? I first started off by using the Expression visualizer that ships in the samples with VS2008 (click for full size):

Query Expression

That might give you a hint as to what is happening, if you are familiar with compiler generated anonymous types, but otherwise that just looks like a bunch of gibberish. You have to open up the app in reflector to really see what is going on with these anonymous types. What this is essentially telling us is exactly what “Skup” in my last post was trying to tell me. That each time you use the “let” keyword it is just generating an anonymous type which wraps the variable assigned in the let statement along with the item being queried.

After thinking a bit I realized that just rewriting this query with explicit Linq methods would probably be the best way to explain this. There is no “let” query method, you have to accomplish all of this through select statements.

var names2 = nameList
    .Select(a => new { a, vowels = new List<string> { "A", "E", "I", "O", "U" } })
    .Select(b => new { b, startsWithVowel = b.vowels.Any(v => b.a.ToUpper().StartsWith(v)) })
    .Select(c => new { c, endsWithVowel = c.b.vowels.Any(v => c.b.a.ToUpper().EndsWith(v)) })
    .Select(d => new { d, fourCharactersLong = d.c.b.a.Length == 4 })
    .Select(e => new { e, fiveCharactersLong = e.d.c.b.a.Length == 5 })
    .Where(w => (w.e.d.c.startsWithVowel || w.e.d.endsWithVowel) 
              && (w.e.fourCharactersLong || w.fiveCharactersLong))
    .Select(s => s.e.d.c.b.a);

So, as you can see, the “let” keyword cleans this up quite a bit. But you can also see what is actually happening. You can also see why Frans said in my last post that when you are running this against Linq to Sql it is going to generate a sub-query for each “let” statement. I hope that this helps a bit when trying to decide whether or not to use the Linq “let” statement.

Be Sociable, Share!

3 comments

  1. Nice post, especially since it came at a time I was trying to wrap my head around the "let" keyword for my open source Linq-to-GPU provider (http://brahma.ananthonline.net). I’d really love to see a similar post about SelectMany, too!

    Cheers!

  2. @Ananth Thanks! And that is a great idea, I think I will write a post on SelectMany. And I will certainly have to check out your Linq-To-GPU provider, keep me informed on your progress.

  3. :) I’ll be eagerly awaiting that article. And as of now, "Select" works just fine on my provider (without "let") and conditionals are working, too! I haven’t released any packages yet (on the new version), but you can check out the sources from Sourceforge.

Leave a comment