我最近在 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)));
}
}
首先,据我所知,这些消息仅显示在 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
不会导致这个问题的原因是Sum
有很多重载——即使你用多个参数删除了重载。
此示例中的 message 有点令人困惑,因为指定 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));
(或使用本地函数。)
所有这些基本上都使编译器的工作变得更轻松,可能会缩短编译时间。如果编译时间对您来说不是问题并且您不想更改代码,则可以隐藏该消息。该消息只是为了让编译器可以帮助解释为什么需要这么长时间。