Обратите внимание на следующий пример, состоящий из определения вложенного элемента XElement и пары выражений Linq. Первое выражение, которое работает, как ожидалось, итеративно выбирает первый и последний XElements на нижнем уровне, выбирая tmp, созданный путем получения ботов (для нижних частей), сохраненных в новых экземплярах анонимного типа для повторного использования имени "боты" . " Второе выражение пытается сделать то же самое, просто используя «Let», но оно вообще не работает. Сначала компилятор жалуется на то, что вывод типа не работает, а затем, когда я добавляю явные типы, он переходит в IObservable и теряется еще больше. Я ожидал, что это будет совершенно незамысловато, и был весьма удивлен и сбит с толку неудачей. Буду признателен, если у кого-нибудь будет минутка посмотреть и посоветовать. Вы можете вставить следующее в LinqPad, добавить ссылку на System.Interactive и увидеть неудачную компиляцию.

var root = new XElement("root",
    new XElement("sub",
        new XElement("bot", new XAttribute("foo", 1)),
        new XElement("bot", new XAttribute("foo", 2))),
    new XElement("sub",
        new XElement("bot", new XAttribute("foo", 3)),
        new XElement("bot", new XAttribute("foo", 4))));
root.Dump("root");

root.Descendants("sub")
    .Select(sub => new {bots = sub.Descendants("bot")})
    .Select(tmp => new{fst = tmp.bots.First(), snd = tmp.bots.Last()})
    .Dump("bottoms")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => new{fst = bots.First(), snd = bots.Last()})
    .Dump("bottoms2")
    ;
0
Reb.Cabin 26 Апр 2011 в 04:57
Разве let не является просто синтаксическим сахаром в языке выражений C #? Мне неизвестен какой-либо метод расширения Let, который вы бы использовали с синтаксисом метода.
 – 
Martin Honnen
26 Апр 2011 в 14:42
В System.Interactive определенно есть метод расширения Let. Вот образец, который компилируется правильно (но не выполняет то, что мне нужно :( root.Descendants ("sub") .Let (bots => bots .Select (bot => bot)) .Dump ("bottomoms2")
 – 
Reb.Cabin
26 Апр 2011 в 18:45
Вот еще одно использование Let, которое работает (но не делает то, что я хочу :) root.Descendants ("sub") .Let (bots => bots.Zip (bots, (a, b) => new {a = a , b = b})) .Dump ("bottom2")
 – 
Reb.Cabin
26 Апр 2011 в 19:41

2 ответа

Лучший ответ

let - это просто ключевое слово, упрощающее преобразования, такие как tmp => new{fst = tmp.bots.First(), snd = tmp.bots.Last()}. Вместо использования метода расширения Let просто используйте метод Select:

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Select(bots => new{fst = bots.First(), snd = bots.Last()})
    .Dump("bottoms2");
1
StriplingWarrior 26 Апр 2011 в 22:25
Я мог бы поклясться, что попробовал это безуспешно, но, должно быть, когда я потерялся в лесу. Спасибо за спасение!
 – 
Reb.Cabin
26 Апр 2011 в 23:08

Хорошо, нашел, хотя не совсем понимаю ответ. Вот два выражения, которые дают желаемые результаты, одно с использованием «Let», а другое с использованием «Select»:

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => bots.Select(bot => new{fst = bot.First(), snd = bot.Last()}))
    .Dump("bottoms2")
    ;

root.Descendants("sub")
    .Select(sub => new {bots = sub.Descendants("bot")})
    .Select(tmp => new{fst = tmp.bots.First(), snd = tmp.bots.Last()})
    .Dump("bottoms")
    ;

Первый «Select», .Select (sub => sub.Descendants («bot»)), в первом из двух выражений, форма «Let», производит перечислимое количество элементов XElements, или, точнее,

System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Xml.Linq.XElement,System.Collections.Generic.IEnumerable`1[System.Xml.Linq.XElement]]

Первое «Выбрать» .Select (sub => new {bots = sub.Descendants («bot»)) во втором из двух выражений, форме «Выбрать», создает перечислимое количество анонимных типов, каждый из которых содержит перечислимое имя «боты» XElements:

System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Xml.Linq.XElement,<>f__AnonymousType0`1[System.Collections.Generic.IEnumerable`1[System....

Мы хотим преобразовать каждое из внутренних перечислимых элементов в пару {fst, snd}. Начнем с того, что следующие два выражения дают одинаковые результаты, но не идентичны семантически, как показано ниже. Единственное различие между этими двумя выражениями состоит в том, что первое имеет «Let» в строке 3, а второе - «Select» в строке 3. Они точно такие же, как выражения «answer», за исключением того, что в них нет внутреннего трансформации.

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => bots.Select(bot => bot))
    .Dump("bottoms3")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Select(bots => bots.Select(bot => bot))
    .Dump("bottoms4")
    ;

Тип «ботов» во внешнем «Let» в первом выражении отличается от типа «ботов» во внешнем «Select» во втором выражении. В «Let» тип «ботов» (примерно) IEnumerable<IEnumerable<XElement>> (его полное название -

System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Xml.Linq.XElement,System.Collections.Generic.IEnumerable`1[System.Xml.Linq.XElement]]

Мы можем увидеть более подробно, выделив внутренности, что каждый «бот» в «ботах» является IEnumerable<XElement>:

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => 
    {
        bots.GetType().Dump("bots in Let"); 
        return bots.Select(bot => bot.GetType());
    })
    .Dump("Types of bots inside the LET")
    ;

Types of bots inside the LET

IEnumerable<Type> (2 items)

typeof (IEnumerable<XElement>)

typeof (IEnumerable<XElement>)

Во внешнем "Select" тип "ботов"

System.Xml.Linq.XContainer+<GetDescendants>d__a

Путем анализа, параллельного приведенному выше, мы видим, что каждый «бот» в «ботах» является IEnumerable чего-то, и что что-то является XElement.

    root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => 
    {
        bots.GetType().Dump("bots in Let"); 
        return bots.Select(bot => bot.GetType());
    })
    .Dump("Types of bots inside the LET")
    ;

Types of bots inside the SELECT

IEnumerable<IEnumerable<Type>> (2 items)

IEnumerable<Type> (2 items)

typeof (XElement)

typeof (XElement)

IEnumerable<Type> (2 items)

typeof (XElement)

typeof (XElement)

Заманчиво думать о них как о семантически одинаковом, но это не так. Существует на один уровень неявной упаковки на уровне типа в форме «Выбрать», чем в форме «Разрешить», или наоборот, в зависимости от вашей точки зрения.

Кроме того, очевидно, что «Let» «запускается» один раз над результатом .Select (sub => sub.Descendants («bot»)), тогда как «Select» запускается несколько раз, по одному разу над каждым результатом. Следующее неверно, потому что он игнорирует этот «уровень упаковки».

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => new{fst = bots.First(), snd = bots.Last()})
    .Dump("bottoms2")
    ;

Как я уже сказал, я еще не до конца понимаю все детали этого явления. Возможно, с помощью еще нескольких примеров и еще одной ночи, которую я потерял из-за этого, я начну развивать более тонкую интуицию по этому поводу. Вот мой полный сценарий LinqPad на тот случай, если вы так сильно заинтересованы поиграть с этой тонкостью:

void Main()
{
Console.WriteLine ("Here is a sample data set, as XML:");
var root = new XElement("root",
new XElement("sub",
    new XElement("bot", new XAttribute("foo", 1)),
    new XElement("bot", new XAttribute("foo", 2))),
new XElement("sub",
    new XElement("bot", new XAttribute("foo", 3)),
    new XElement("bot", new XAttribute("foo", 4))));
root.Dump("root");

Console.WriteLine ("The following two expressions produce the same results:");

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => bots.Select(bot => new{fst = bot.First(), snd = bot.Last()}))
    .Dump("LET form: bottoms1")
    ;

root.Descendants("sub")
    .Select(sub => new {bots = sub.Descendants("bot")})
    .Select(tmp => new{fst = tmp.bots.First(), snd = tmp.bots.Last()})
    .Dump("SELECT form: bottoms2")
    ;

Console.WriteLine ("Analysis of LET form");

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Dump("Top-Level Select in the \"Let\" form:")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .GetType()
    .Dump("Type of the top-Level Select in the \"Let\" form:")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => bots.Select(bot => bot))
    .Dump("Let(bots => bots.Select(bot => bot))")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Let(bots => 
    {
        bots.GetType().Dump("bots in Let"); 
        return bots.Select(bot => bot.GetType());
    })
    .Dump("Types of bots inside the LET")
    ;

Console.WriteLine ("Analysis of SELECT form");

root.Descendants("sub")
    .Select(sub => new {bots = sub.Descendants("bot")})
    .Dump("Top-level Select in the \"Select\" form:")
    ;

root.Descendants("sub")
    .Select(sub => new {bots = sub.Descendants("bot")})
    .GetType()
    .Dump("Type of the top-level Select in the \"Select\" form:")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Select(bots => bots.Select(bot => bot))
    .Dump("bots => bots.Select(bot => bot)")
    ;

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Select(bots =>         
    {
        bots.GetType().Dump("bots in Select"); 
        return bots.Select(bot => bot.GetType());
    })
    .Dump("Types of bots inside the SELECT")
    ;
}
0
Reb.Cabin 26 Апр 2011 в 22:22
У вас очень сложные вещи. Смотрите мой ответ.
 – 
StriplingWarrior
26 Апр 2011 в 22:25