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



请考虑以下示例,该示例由嵌套XElement的定义和一对Linq表达式组成。第一个表达式如预期的那样工作,通过在获取bot(用于bottom)所生成的tmp中进行选择,迭代地获取底层的第一个和最后一个xelement,这些bot存储在匿名类型的新实例中,以便重用名称"bots"。第二个表达式试图做同样的事情,只是使用了"Let",但它根本不起作用。首先,编译器会抱怨类型推断不工作,然后,当我放入显式类型时,它会转到IObservable中,变得更加混乱。我原以为这完全是直截了当的,但我对失败感到非常惊讶和困惑。如果有人有时间看一下并提供建议,我将不胜感激。您可以将以下内容粘贴到LinqPad中,添加对System的引用。交互,并查看编译失败。

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只是一个关键字,它简化了像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",另一个使用"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"形式中,产生一个xelement的可枚举对象的可枚举对象,或者,更准确地说,

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}对。首先要注意,下面两个表达式产生相同的结果,但在语义上并不相同,如下所示。这两个表达式之间唯一的区别是,第一个在第三行有"Let",第二个在第三行有"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"中,"bot"的类型(大致)为IEnumerable<IEnumerable<XElement>>(其全名为

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

我们可以更详细地看到,通过在"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 LET

IEnumerable<Type> (2 items)

typeof (IEnumerable<XElement>)

typeof (IEnumerable<XElement>)

在外部的"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<IEnumerable<Type>> (2 items)

IEnumerable<Type> (2 items)

typeof (XElement)

typeof (XElement)

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

相关内容

  • 没有找到相关文章