将匿名 lambda 强制转换为强类型委托是否会禁用编译器缓存?



我正在尝试了解编译器驱动的委托缓存以避免内存分配的边缘情况。

例如,据我了解,这个委托被缓存到单个实例并重用,因为它不会关闭任何局部变量:

int[] set = new [] { 1, 2, 3, 4, 5, 6 };
var subset = set.Where(x => x % 2 == 0);

现在我有一些情况,我生成的代码可能想要直接调用委托,因此匿名方法无效 C#,如下所示:

var result = (x => x % 2 == 0).Invoke(5); // Invalid

为了规避这种情况,我看到了两个选项:

  1. 使用构造函数:
var result = (new Func<int, bool>(x => x % 2 == 0)).Invoke(5);
  1. 强制转换匿名委托:
var result = ((Func<int, bool>)(x => x % 2 == 0)).Invoke(5);

我假设编译器不会在选项 #1 中缓存委托,但我不确定它是否会在 #2 中缓存。

这在任何地方都有记录吗?

我假设编译器不会在选项 #1 中缓存委托,但我不确定它是否会在 #2 中缓存。

事实上,在这两种情况下都可以,而且它们被绑在一起。

从 ECMA C# 5 规范的第 7.6.10.5 节:

新 D(E) 形式的委托创建表达式的绑定时处理(其中 D 是委托类型,E 是表达式)包括以下步骤:

  • 如果 E 是匿名函数
  • ,则委托创建表达式的处理方式与从 E 到 D 的匿名函数转换 (§6.5) 相同。

所以基本上两者的处理方式相同。在这两种情况下,它都可以被缓存。是的,"新并不一定意味着新"这很奇怪。

为了说明这一点,让我们编写一个非常简单的程序:

using System;
public class Program
{
public static void Main()
{
var func = new Func<int, bool>(x => x % 2 == 0);
}
}

这是我的机器上Main方法的 IL(诚然,使用 C# 8 预览版编译器构建,但我希望在一段时间内也是如此):

.method public hidebysig static void  Main() cil managed
{
.entrypoint
// Code size       29 (0x1d)
.maxstack  8
IL_0000:  ldsfld     class [mscorlib]System.Func`2<int32,bool> Program/'<>c'::'<>9__0_0'
IL_0005:  brtrue.s   IL_001c
IL_0007:  ldsfld     class Program/'<>c' Program/'<>c'::'<>9'
IL_000c:  ldftn      instance bool Program/'<>c'::'<Main>b__0_0'(int32)
IL_0012:  newobj     instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object,
                native int)
IL_0017:  stsfld     class [mscorlib]System.Func`2<int32,bool> Program/'<>c'::'<>9__0_0'
IL_001c:  ret
} // end of method Program::Main

这实际上是:

Func<int, bool> func;
func = cache;
if (func == null)
{
func = new Func<int, bool>(GeneratedPrivateMethod);
cache = func;
}

最新更新