214

Using the C# compilers query comprehension features, you can write code like:

var names = new string[] { "Dog", "Cat", "Giraffe", "Monkey", "Tortoise" };
var result =
    from animalName in names
    let nameLength = animalName.Length
    where nameLength > 3
    orderby nameLength
    select animalName; 

In the query expression above, the let keyword allows a value to be passed forward to the where and orderby operations without duplicate calls to animalName.Length.

What is the equivalent set of LINQ extension method calls that achieves what the "let" keyword does here?

Keltex
  • 26,220
  • 11
  • 79
  • 111
LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • 11
    FYI, the C# 3.0 specification explains every query comprehension translation rule in excruciating detail. – Eric Lippert Jul 07 '09 at 14:56
  • 17
    and for those who find the spec heavy going, Jon Skeet's C# in Depth covers it too ;-p – Marc Gravell Jul 07 '09 at 15:06
  • The C# Language Specifications are downloadable Word documents whose content is not indexed by search engines and is neither linkable nor browseable online. It would be a great help if the specifications were available online. – Olivier Jacot-Descombes Feb 20 '16 at 16:57

4 Answers4

281

Let doesn't have its own operation; it piggy-backs off of Select. You can see this if you use "reflector" to pull apart an existing dll.

it will be something like:

var result = names
        .Select(animalName => new { nameLength = animalName.Length, animalName})
        .Where(x=>x.nameLength > 3)
        .OrderBy(x=>x.nameLength)
        .Select(x=>x.animalName);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 23
    You can also use the little "lambda" button in the result pane of LinqPad to see the generated code *if* you start with a Queryable. In other words, if you change your first line to var names = new string [] { "Dog", ... }.AsQueryable(); then run the whole thing in LinqPad, click the little lambda button, you will see generated code virtually identical to Marc's answer. – Reb.Cabin Apr 30 '11 at 00:10
  • 3
    I needed to use the `.Dump()` extension method in LinqPad to see the resulting lambda. – justanotherdev Dec 21 '15 at 07:08
98

There's a good article here

Essentially let creates an anonymous tuple. It's equivalent to:

var result = names.Select(
  animal => new { animal = animal, nameLength = animal.Length })
.Where(x => x.nameLength > 3)
.OrderBy(y => y.nameLength)
.Select(z => z.animal);
Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
Keltex
  • 26,220
  • 11
  • 79
  • 111
  • I quote the article above `it seems prudent to recommend against using the let keyword in cases where you do not need to transform a variable` – JB. Jul 21 '15 at 14:03
  • 1
    I quote it further: `This could be considered a micro-optimisation` – Monsignor Sep 12 '19 at 09:58
7

There is also a .Let extension method in System.Interactive, but its purpose is to introduce a lambda expression to be evaluated 'in-line' in a fluent expression. For instance, consider (in LinqPad, say) the following expression that creates new random numbers every time it's executed:

var seq = EnumerableEx.Generate(
    new Random(),
    _ => true,
    _ => _,
    x => x.Next());

To see that new random samples show up every time, consider the following

seq.Zip(seq, Tuple.Create).Take(3).Dump();

which produces pairs in which the left and right are different. To produce pairs in which the left and right are always the same, do something like the following:

seq.Take(3).ToList().Let(xs => xs.Zip(xs, Tuple.Create)).Dump(); 

If we could invoke lambda expressions directly, we might write

(xs => xs.Zip(xs, Tuple.Create))(seq.Take(3).ToList()).Dump();

But we can't invoke lambda expressions as if they were methods.

Reb.Cabin
  • 5,426
  • 3
  • 35
  • 64
1

about Code equivalent to the 'let' keyword in chained LINQ extension method calls

above comment is no more valid

var x = new List<int> { 2, 3, 4, 5, 6 }.AsQueryable();
(from val in x
let val1 = val
let val2 = val + 1
where val2 > val1
select val
).Dump();

produces

System.Collections.Generic.List`1[System.Int32]
.Select(
  val =>
     new
     {
         val = val,
         val1 = val
     }
)
.Select(
  temp0 =>
     new
     {
         temp0 = temp0,
         val2 = (temp0.val + 1)
     }
)
.Where(temp1 => (temp1.val2 > temp1.temp0.val1))
.Select(temp1 => temp1.temp0.val)

so multiple let are optimized now

Not Important
  • 762
  • 6
  • 22