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.
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.
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 🙁
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.
@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!
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.
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();
Is there a performance penalty for using the "let" keyword?
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.
@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.
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 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.
Can “let” be used with Fluent Syntax ?
Say
string[] names = {“dan”,”don”,”sam”,”rob”, “jeni”};
Now if I want to find which names ends with a vowel and try to use let keyword, how can I do that using fluent syntax or is it just for query syntax?