我试图了解编译器支持的委托缓存的一种极端情况,以避免内存分配。
例如,据我所知,该委托被缓存到单个实例中并被重用,因为它没有关闭任何局部变量:
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
为避免这种情况,我看到两个选择:
var result = (new Func<int, bool>(x => x % 2 == 0)).Invoke(5);
var result = ((Func<int, bool>)(x => x % 2 == 0)).Invoke(5);
我假设编译器不会在选项#1中缓存委托,但是我不确定是否在#2中缓存委托。
此文件记录在任何地方吗?
答案 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;
}