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

时间:2019-03-26 21:28:56

标签: c# lambda delegates

我试图了解编译器支持的委托缓存的一种极端情况,以避免内存分配。

例如,据我所知,该委托被缓存到单个实例中并被重用,因为它没有关闭任何局部变量:

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 个答案:

答案 0 :(得分:9)

  

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

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

根据ECMA C#5规范的第7.6.10.5节:

  

形式为new 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;
}