Linq-to-Objects 和 Linq-to-XML 中的 Let 语义问题

发布于 2024-11-03 14:23:13 字数 1030 浏览 8 评论 0 原文

请考虑以下示例,其中包含嵌套 XElement 的定义和一对 Linq 表达式。第一个表达式按预期工作,通过选择通过获取机器人(用于底部)创建的 tmp,迭代获取底层的第一个和最后一个 XElement,存储在匿名类型的新实例中,以便重用名称“bots” ”。第二个表达式尝试做同样的事情,只是使用“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")
    ;

Please consider the following sample, consisting of a definition of a nested XElement and a pair of Linq expressions. The first expression, which works as expected, iteratively fetches the first and last XElements at the bottom level by selecting over a tmp made by getting the bots (for bottoms), stored in new instances of an anonymous type for reuse of the name "bots." The second expression attempts to do the same thing, just using a "Let", but it does not work at all. First, the compiler complains about type inference not working, and then, when I put in explicit types, it goes off into IObservable and gets even more lost. I expected this to be utterly straightforward and was quite surprised and flummoxed at the failure. I'd be grateful if anyone has a moment to look and advise. You can paste the following into LinqPad, add a reference to System.Interactive, and see the failed compilation.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

成熟稳重的好男人 2024-11-10 14:23:13

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

let is just a keyword that simplifies transforms like tmp => new{fst = tmp.bots.First(), snd = tmp.bots.Last()}. Rather than using a Let extension method, just use the Select method:

root.Descendants("sub")
    .Select(sub => sub.Descendants("bot"))
    .Select(bots => new{fst = bots.First(), snd = bots.Last()})
    .Dump("bottoms2");
人间不值得 2024-11-10 14:23:13

好的,找到了,虽然我不完全理解答案。以下是产生所需结果的两个表达式,一个使用“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”形式,生成匿名类型的可枚举,每个匿名类型都包含一个名为“bots”的 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")
    ;

第一个表达式中外部“Let”中的“bots”类型与第二个表达式中外部“Select”中“bots”的类型不同。在“Let”中,“bots”的类型(大致)是 IEnumerable> (它的全名是

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

我们可以通过 Selecting over the insides 看到更多细节,每个“ “bots”中的“bot”是一个 IEnumerable

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

LET 内的机器人类型

IEnumerable (2 items)

typeof (IEnumerable)

typeof (IEnumerable)

在外部“Select”中, “bots”的类型是

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

通过与上面的并行分析,我们看到“bots”中的每个“bot”都是某个东西的 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")
    ;

SELECT 中的机器人类型

IEnumerable> (2 items)

IEnumerable (2 items)

typeof (XElement)

typeof (XElement)

IEnumerable (2 项)

typeof; (XElement)

typeof (XElement)

人们很容易认为它们在语义上是相同的,但事实并非如此。 “Select”形式中的类型级别隐式包装比“Let”形式中的隐式包装多一级,反之亦然,具体取决于您的观点。

另外,显然,“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")
    ;
}

ok, found it, though I don't completely understand the answer. Here are two expressions that produce the desired results, one using "Let" and the other using "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")
    ;

The first "Select," .Select(sub => sub.Descendants("bot")), in the first of the two expressions, the "Let" form, produces an enumerable of enumerables of XElements, or, more precisely,

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

The first "Select," .Select(sub => new {bots = sub.Descendants("bot")), in the second of the two expressions, the "Select" form, produces an enumerable of anonymous types, each of which contains an enumerable, named "bots" of XElements:

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

We want to transform each of the inner enumerables into a {fst, snd} pair. Begin by noting that the following two expressions produce the same results, but are not semantically identical, as we show below. The only difference between these two expressions is that the first one has "Let" on line 3, and the second one has "Select" on line 3. They're just like the "answer" expressions except they don't have the inner transformations.

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

The type of "bots" in the outer "Let" in the first expression differs from the type of "bots" in the outer "Select" in the second expression. In the "Let," the type of "bots" is (roughly) IEnumerable<IEnumerable<XElement>> (its full name is

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

We can see in more detail by Selecting over the insides that each "bot" in "bots" is an 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>)

In the outer "Select," the type of "bots" is

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

By a parallel analysis to the above, we see that each "bot" in "bots" is an IEnumerable of something, and that something is an 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)

It's tempting to think of these as semantically the same, but they're not. There is one level more of implicit packaging at the type level in the "Select" form than there is in the "Let" form, or vice versa depending on your point of view.

Also, obviously, the "Let" "runs" once over the result of .Select(sub => sub.Descendants("bot")), whereas the "Select" runs multiple times, once over each result
The following is wrong because it ignores that "level of packaging."

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

As I said, I don't completely understand every detail of this phenomenon, yet. Perhaps with a few more examples and another night's lost sleep over it, I'll begin to develop a more refined intuition about it. Here is my full LinqPad script in case you're so motivated to play with this subtlety:

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")
    ;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文