离散的匿名方法共享一个类?

我在这里玩了Eric Lippert的Ref<T>课。 我注意到在IL中,它看起来像两个匿名方法使用相同的生成类,即使这意味着该类有一个额外的变量。

虽然只使用一个新的类定义似乎有些合理,但我觉得很奇怪,只有一个<>c__DisplayClass2实例被创建。 这似乎意味着Ref<T>两个实例都引用相同的<>c__DisplayClass2并不意味着只有在收集了vart1才能收集y ,这可能比joik返回之后发生得晚得多? 毕竟,不能保证一些白痴不会写一个直接通过vart1 joik返回来访问y的函数(直接在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;
}

运行IL DASM确认vart1vart2都使用了<>__DisplayClass2 ,其中包含一个用于x和y的公共字段。 joik的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

是的,匿名方法的MS实现有效地创建一个隐藏类,它需要从每个级别的范围捕获变量,并捕获该范围内的所有相关变量。 我相信这是为了简单起见,但它的确可以增加一些物体的不必要的寿命。

每个匿名方法只会捕获它实际感兴趣的变量会更加优雅。但是,这会让生活变得更加复杂......如果一个匿名方法捕获xy ,一个捕获x并捕获y ,那么您需要三个类:一个用于捕获x ,一个用于捕获y ,另一个用于组成这两个(但不只是有两个变量)。 棘手的一点是,对于任何单个变量实例,该变量需要在一个地方居住,以便引用它的所有内容都可以看到相同的值,而不管它是否发生更改。

这并不违反规范,但它可能被认为是不幸的 - 我不知道它是否真的在现实生活中被人们咬了,但这当然是可能的。

好消息是,如果C#团队决定改进这一点,他们应该能够以完全向后兼容的方式这样做,除非某些木偶依赖于不必要地延长生命期。


Jon当然是对的。 这通常会导致的问题是:

void M()
{
    Expensive e = GetExpensive();
    Cheap c = GetCheap();
    D longLife = ()=>...c...;
    D shortLife = ()=>...e...;
    ...
}

所以我们有一个昂贵的资源,它的生命周期取决于longLife的生命周期,即使shortlife是早期收集的。

这很不幸,但很常见。 JScript和VB中的闭包实现有同样的问题。

我想在假想的C#未来版本中解决它,但我不保证。 做这件事的一个显而易见的方法是根据哪些lambda被捕获,并根据每个等价类生成闭包类,而不是单个闭包类来确定闭包变量的等价类。

我们还可以通过分析写入的封闭变量来做些事情。 正如乔恩所说,我们目前受限于我们需要捕捉变量而不是价值。 如果我们确定了在创建闭包之后永远不会写入的变量,那么我们可以在代码生成策略中更加灵活,并将这些变为闭合值而不是闭合变量。

链接地址: http://www.djcxy.com/p/51589.html

上一篇: Discrete Anonymous methods sharing a class?

下一篇: Template of wrapper for variadic template functions