Bri*_*ian 10 c# delegates anonymous-methods
我在这里和Eric Lippert的Ref<T>
班级玩了一下.我注意到在IL中看起来两个匿名方法都使用相同的生成类,即使这意味着类有一个额外的变量.
虽然只使用一个新的类定义似乎有点合理,但我觉得只<>c__DisplayClass2
创建一个实例非常奇怪.这似乎暗示两个实例Ref<T>
都引用相同的<>c__DisplayClass2
意思并不意味着y
在收集之前无法vart1
收集,这可能比joik
返回之后发生得晚得多?毕竟,不能保证一些白痴不会写一个直接y
通过vart1
aftrer joik
返回的函数(直接在IL中).也许这甚至可以通过反射而不是疯狂的IL来完成.
sealed class Ref<T>
{
public delegate T Func<T>();
private readonly Func<T> getter;
public Ref(Func<T> getter)
{
this.getter = getter;
}
public T Value { get { return getter(); } }
}
static Ref<int> joik()
{
int[] y = new int[50000];
int x = 5;
Ref<int> vart1 = new Ref<int>(delegate() { return x; });
Ref<int[]> vart2 = new Ref<int[]>(delegate() { return y; });
return vart1;
}
Run Code Online (Sandbox Code Playgroud)
运行IL DASM确认vart1
并且vart2
都使用了<>__DisplayClass2
,其中包含x和y的公共字段.笑话的IL:
.method private hidebysig static class Program/Ref`1<int32>
joik() cil managed
{
// Code size 72 (0x48)
.maxstack 3
.locals init ([0] class Program/Ref`1<int32> vart1,
[1] class Program/Ref`1<int32[]> vart2,
[2] class Program/'<>c__DisplayClass2' '<>8__locals3',
[3] class Program/Ref`1<int32> CS$1$0000)
IL_0000: newobj instance void Program/'<>c__DisplayClass2'::.ctor()
IL_0005: stloc.2
IL_0006: nop
IL_0007: ldloc.2
IL_0008: ldc.i4 0xc350
IL_000d: newarr [mscorlib]System.Int32
IL_0012: stfld int32[] Program/'<>c__DisplayClass2'::y
IL_0017: ldloc.2
IL_0018: ldc.i4.5
IL_0019: stfld int32 Program/'<>c__DisplayClass2'::x
IL_001e: ldloc.2
IL_001f: ldftn instance int32 Program/'<>c__DisplayClass2'::'<joik>b__0'()
IL_0025: newobj instance void class Program/Ref`1/Func`1<int32,int32>::.ctor(object,
native int)
IL_002a: newobj instance void class Program/Ref`1<int32>::.ctor(class Program/Ref`1/Func`1<!0,!0>)
IL_002f: stloc.0
IL_0030: ldloc.2
IL_0031: ldftn instance int32[] Program/'<>c__DisplayClass2'::'<joik>b__1'()
IL_0037: newobj instance void class Program/Ref`1/Func`1<int32[],int32[]>::.ctor(object,
native int)
IL_003c: newobj instance void class Program/Ref`1<int32[]>::.ctor(class Program/Ref`1/Func`1<!0,!0>)
IL_0041: stloc.1
IL_0042: ldloc.0
IL_0043: stloc.3
IL_0044: br.s IL_0046
IL_0046: ldloc.3
IL_0047: ret
} // end of method Program::joik
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 13
是的,匿名方法的MS实现有效地为每个范围的范围创建一个隐藏类,它需要从中捕获变量,并从该范围捕获所有相关变量.我相信这是为了简单起见,但确实可以不必要地增加某些对象的生命周期.
这将是更优雅每个匿名方法只能捕捉它是真正感兴趣的变量.然而,这样可以使生活相当复杂......如果一个匿名方法捕获x
和y
,一个捕获x
和一个捕捉y
,您需要三个类:一个用于捕获x
,一个用于捕获y
,一个用于合成两个(但不只是有两个变量).棘手的一点是,对于任何单个变量实例化,该变量需要只存在于一个位置,以便引用它的所有内容看到相同的值,无论它是什么变化.
这并没有违反规范,但它可能被认为是不幸的 - 我不知道它是否真的在现实生活中被咬过,但它肯定是可能的.
好消息是,如果C#团队决定改进这一点,他们应该能够以完全向后兼容的方式这样做,除非一些布偶依赖于不必要地延长生命周期.
乔恩当然是对的.这通常导致的问题是:
void M()
{
Expensive e = GetExpensive();
Cheap c = GetCheap();
D longLife = ()=>...c...;
D shortLife = ()=>...e...;
...
}
Run Code Online (Sandbox Code Playgroud)
所以我们有一个昂贵的资源,其寿命现在取决于longLife的生命周期,即使shortLife是早期收集的.
这是不幸的,但很常见.JScript和VB中闭包的实现具有相同的问题.
我想在假设的未来版本的C#中解决它,但我不保证.显而易见的方法是根据捕获的lambdas来识别封闭变量的等价类,并为每个等价类生成一个闭包类,而不是单个闭包类.
我们也可以通过分析写入的封闭变量来做些什么.正如Jon指出的那样,我们目前受限于捕获变量而不是值.如果我们确定在创建闭包后永远不会写入的变量,并且将它们变为封闭值而不是封闭变量,那么我们的代码生成策略可以更灵活.
归档时间: |
|
查看次数: |
830 次 |
最近记录: |