Код, эквивалентный ключевому слову let, в цепочечных вызовах методов расширения LINQ

192

Используя функции понимания запросов компиляторами C #, вы можете написать код:

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; 

В приведенном выше выражении запроса letключевое слово позволяет передавать значение в операции where и orderby без повторных вызовов animalName.Length.

Каков эквивалентный набор вызовов метода расширения LINQ, который достигает того, что здесь делает ключевое слово "let"?

LBushkin
источник
11
К сведению, спецификация C # 3.0 объясняет каждое правило перевода понимания запросов в мучительных деталях.
Эрик Липперт
17
и для тех, кто считает, что спецификация тяжела, C # Jon Skeet в глубине покрывает это тоже ;-p
Марк
Языковые спецификации C # являются загружаемыми документами Word, содержимое которых не индексируется поисковыми системами и не доступно ни для просмотра, ни для просмотра в Интернете. Было бы очень полезно, если бы спецификации были доступны онлайн.
Оливье Жако-Дескомбс

Ответы:

250

Пусть не имеет своей собственной операции; это свиные спины Select. Вы можете увидеть это, если вы используете «рефлектор», чтобы отделить существующую DLL.

это будет что- то вроде:

var result = names
        .Select(animalName => new { nameLength = animalName.Length, animalName})
        .Where(x=>x.nameLength > 3)
        .OrderBy(x=>x.nameLength)
        .Select(x=>x.animalName);
Марк Гравелл
источник
4
Вау, я не знал, что вы можете автоматически инкапсулировать, используя новый оператор.
Дэвид Пфеффер
19
Вы также можете использовать маленькую кнопку «лямбда» на панели результатов LinqPad, чтобы увидеть сгенерированный код, если вы начинаете с Queryable. Другими словами, если вы измените свою первую строку на var names = new string [] {"Dog", ...} .AsQueryable (); затем запустите все это в LinqPad, нажмите маленькую лямбда-кнопку, и вы увидите сгенерированный код, практически идентичный ответу Марка.
Reb.Cabin
3
Мне нужно было использовать .Dump()метод расширения в LinqPad, чтобы увидеть результирующую лямбду.
justanotherdev
88

Там хорошая статья здесь

По сути letсоздает анонимный кортеж. Это эквивалентно:

var result = names.Select(
  animal => new { animal = animal, nameLength = animal.Length })
.Where(x => x.nameLength > 3)
.OrderBy(y => y.nameLength)
.Select(z => z.animal);
Keltex
источник
Я цитирую статью вышеit seems prudent to recommend against using the let keyword in cases where you do not need to transform a variable
JB. С моникой.
Я цитирую это далее:This could be considered a micro-optimisation
Монсеньор
7

В System.Interactive также есть метод расширения .Let, но его цель - ввести лямбда-выражение, которое будет оцениваться «in-line» в беглом выражении. Например, рассмотрим (скажем, в LinqPad) следующее выражение, которое создает новые случайные числа при каждом его выполнении:

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

Чтобы увидеть, что новые случайные выборки появляются каждый раз, рассмотрите следующее

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

который производит пары, в которых левый и правый различны. Чтобы создать пары, в которых левый и правый всегда одинаковы, сделайте что-то вроде следующего:

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

Если бы мы могли вызывать лямбда-выражения напрямую, мы могли бы написать

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

Но мы не можем вызывать лямбда-выражения, как если бы они были методами.

Reb.Cabin
источник
1

о коде, эквивалентном ключевому слову let в вызовах метода расширения LINQ

вышеуказанный комментарий больше не действителен

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

производит

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)

так что несколько letоптимизированы сейчас

Не важный
источник