我最近在 C# 中看到以下编译器消息:

CS9236 编译需要至少绑定 lambda 表达式 100 次。请考虑使用显式参数类型声明 lambda 表达式,或者如果包含的方法调用是泛型的,请考虑使用显式类型参数。

不幸的是,帮助链接(目前)已损坏。您能举例解释一下吗?为什么用显式类型来扩大表达式是个好主意?

中的一个例子

using System.Linq;
class Container
{
    public IEnumerable<Container> Items;
    public int Value;
}
class Program
{
    static void Main()
    {
        var list = new List<Container>();
        _ = list.Sum(
            a => a.Items.Sum(
                b => b.Items.Sum(
                    c => c.Value)));
    }
}

18

  • 4
    既然您看到了编译器消息,那么可能至少可以用一个例子来演示这个问题?这样问题就更清楚了。


    – 

  • 2
    那么,你真的在​​代码中看到过这种情况吗?或者,当你说“我最近看到以下编译器消息”时,你的意思是“浏览 Roslyn 源代码时”吗?这很可能是一个非常隐蔽的错误,只有在调试 Roslyn 本身时才会出现。(毕竟,这是在谈论编译器的性能。)仅使用“dotnet build”命令构建测试中的代码不会提示该错误。


    – 


  • 2
    “我在专有代码中看到了它,我不能公开分享。”但是你能在非专有代码中重现它吗?测试中的示例看起来你不需要任何远程机密来重现它 – 但你可能需要检查编译器设置等。正如我所说,仅从测试中构建代码不会为我产生这些警告,因此可能需要更多的东西。如果不能重现它,就很难深入解释某件事。


    – 

  • 1
    啊哈 -在 VS 中编译该代码时,我在“消息”中看到它(但不是警告或错误)。从命令行构建时我仍然看不到任何东西。现在我终于有了一个重现,我会看看我是否可以解释它。(但如果您从一开始就包含代码并解释它仅在 VS 中可见,我们就可以避免很多来回的争论……)


    – 

  • 1
    @phuzi:我怀疑当我发表评论时它已经重新打开了,我只是没有检查:)(我想我直接滚动到了评论。)该回答了。


    – 


最佳答案
1

首先,据我所知,这些消息仅出现在 Visual Studio 中 – 可能有办法dotnet build报告它们,但我还没有找到。

该示例可以稍微简化,仅使用两个 lambda 表达式:

#pragma warning disable 649
class Container
{
    public IEnumerable<Container> Items;
    public int Value;
}
class Program
{
    static void Main()
    {
        var list = new List<Container>();
        _ = list.Sum(
            a => a.Items.Sum(
                b => b.Value));
    }
}

这仅会产生一条消息,但更容易理解。

该消息与代码是否有效无关 – 它实际上只是说“嘿,这给编译器带来了很多工作 – 你可以让它的工作更轻松。” 在某些情况下,这反过来可以减少编译时间。

发生这种情况的原因在于 C# 的两个最复杂的方面(从规范角度来看,我怀疑从编译器实现角度来看):

  • 泛型类型推断
  • 重载决策

具体来说,虽然 C# 中的大多数表达式自然具有类型,但对于 lambda 表达式,编译器必须有效地查看它是否可以将给定的 lambda 表达式转换为候选参数类型,以检查每个重载的适用性。当存在嵌套的lambda 时,这意味着整个过程最终会呈指数级增长。

Sum导致此问题的原因Select是(比如说)没有– 即使您使用多个参数切断过载。

对于此示例,该消息有点令人困惑,因为指定 lambda 表达式参数类型和类型参数没有帮助。这仍然给出相同的消息,例如:

_ = list.Sum<Container>(
    (Container a) => a.Items.Sum<Container>(
        (Container b) => b.Value));

告诉编译器 lambda 表达式的目标类型完全可行:

_ = list.Sum(
    a => a.Items.Sum(
        (Func<Container, int>) (b => b.Value)));

或者您可以选择使用局部变量以不同的方式实现它:

var valueSelector = (Container b) => b.Value;
_ = list.Sum(
    a => a.Items.Sum(valueSelector));

或者

Func<Container, int> valueSelector = b => b.Value;
_ = list.Sum(
    a => a.Items.Sum(valueSelector));

(或者使用本地函数。)

所有这些基本上都使编译器的工作变得更轻松,从而可能缩短编译时间。如果编译时间对您来说不是问题,并且您不想更改代码,则可以隐藏该消息。显示该消息只是为了让编译器能够帮助解释为什么编译时间这么长。