Проблемы с семантикой Let в Linq-to-Objects и Linq-to-XML - PullRequest
0 голосов
/ 26 апреля 2011

Пожалуйста, рассмотрите следующий пример, состоящий из определения вложенного элемента XElement и пары выражений Linq. Первое выражение, которое работает, как и ожидалось, итеративно извлекает первый и последний XElements на нижнем уровне, выбирая более tmp, сделанный путем получения ботов (для дна), сохраненных в новых экземплярах анонимного типа для повторного использования имени «боты» «. Второе выражение пытается сделать то же самое, просто используя «Позволить», но оно не работает вообще. Во-первых, компилятор жалуется, что вывод типов не работает, а затем, когда я добавляю явные типы, он уходит в 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")
    ;

Ответы [ 2 ]

1 голос
/ 26 апреля 2011

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");
0 голосов
/ 26 апреля 2011

хорошо, нашел это, хотя я не совсем понимаю ответ. Вот два выражения, которые дают желаемые результаты, одно с использованием «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» .Select (sub => new {bots = sub.Descendants («bot»)), во втором из двух выражений, форме «Select», производит перечисление анонимных типов, каждый из которых содержит перечислимые именованные «боты» XElements:

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

Мы хотим преобразовать каждое внутреннее перечислимое число в пару {fst, snd}. Начнем с того, что отметим, что следующие два выражения дают одинаковые результаты, но не являются семантически идентичными, как показано ниже. Единственное различие между этими двумя выражениями заключается в том, что в первом из них в строке 3 указано «Let», а во втором - в строке 3 «Select». Они похожи на выражения «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")
    ;

Тип «ботов» во внешнем «Позволить» в первом выражении отличается от типа «ботов» во внешнем «Выборе» во втором выражении. В «Позволить» тип «ботов» (примерно) 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")
    ;
}
...