【问题标题】:Problems with Let semantics in Linq-to-Objects and Linq-to-XMLLinq-to-Objects 和 Linq-to-XML 中的 Let 语义问题
【发布时间】:2011-04-26 00:57:42
【问题描述】:

请考虑以下示例,其中包含嵌套 XElement 的定义和一对 Linq 表达式。第一个表达式按预期工作,迭代地获取底层的第一个和最后一个 XElement,方法是选择通过获取机器人(用于底部)生成的 tmp,存储在匿名类型的新实例中以重用名称“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")
    ;

【问题讨论】:

  • let 不只是 C# 表达式语言中的语法糖吗?我不知道您将在方法语法中使用任何 Let 扩展方法。
  • System.Interactive 中肯定有 Let 扩展方法。这是一个正确编译的示例(但没有做我想要的:( root.Descendants("sub") .Let(bots => bots .Select(bot => bot)) .Dump("bottoms2")
  • 这是 Let 的另一种用法(但不能满足我的要求:) root.Descendants("sub") .Let(bots => bots.Zip(bots, (a, b ) => 新 {a = a, b = b})) .Dump("bottoms2")

标签: linq linq-to-xml linq-to-objects let system.interactive


【解决方案1】:

let 只是一个简化转换的关键字,如@9​​87654322@。与其使用Let 扩展方法,不如使用Select 方法:

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

【讨论】:

  • 我可以发誓我尝试过但没有成功,但一定是在我迷失在森林里的时候。感谢您的救援!
【解决方案2】:

好的,找到了,虽然我不完全理解答案。这里有两个产生所需结果的表达式,一个使用“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")
    ;

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

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

我们可以通过在内部选择“bots”中的每个“bot”来更详细地看到IEnumerable&lt;XElement&gt;:

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&lt;Type&gt; (2 items)

typeof (IEnumerable&lt;XElement&gt;)

typeof (IEnumerable&lt;XElement&gt;)

在外部的“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")
    ;

Types of bots inside the SELECT

IEnumerable&lt;IEnumerable&lt;Type&gt;&gt; (2 items)

IEnumerable&lt;Type&gt; (2 items)

typeof (XElement)

typeof (XElement)

IEnumerable&lt;Type&gt; (2 items)

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

【讨论】:

  • 你的事情过于复杂了。查看我的回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多