【问题标题】:Discrete Anonymous methods sharing a class?共享一个类的离散匿名方法?
【发布时间】:2011-04-22 13:20:48
【问题描述】:

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

虽然只使用一个新的类定义似乎有些合理,但我觉得很奇怪的是只创建了一个 <>c__DisplayClass2 的实例。这似乎意味着Ref<T> 的两个实例都引用了相同的<>c__DisplayClass2,这是否意味着在收集vart1 之前无法收集y,这可能比joik 返回之后发生得晚得多?毕竟,不能保证某些白痴不会编写一个函数(直接在 IL 中)直接访问 y 到 vart1 之后 joik 返回。也许这甚至可以通过反射而不是通过疯狂的 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 确认 vart1 和 vart2 都使用了 &lt;&gt;__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

【问题讨论】:

    标签: c# delegates anonymous-methods


    【解决方案1】:

    是的,匿名方法的 MS 实现有效地为每个范围级别创建一个隐藏类,它需要从中捕获变量,并从该范围捕获所有相关变量。我相信这样做是为了简单起见,但它确实会不必要地增加某些对象的生命周期。

    每个匿名方法只捕获它实际感兴趣的变量会更优雅。但是,这可能会使生活相当更加复杂......如果一种匿名方法捕获x 和y,一种捕获x,一种捕获y,您需要三个类:一种用于捕获x,一种用于捕获y,另一种用于编写两个(但不是只有两个变量)。棘手的一点是,对于任何单个变量的实例化,该变量都需要恰好位于一个位置,以便引用它的所有内容都看到相同的值,无论它发生什么变化。

    这并没有以任何方式违反规范,但它可能被认为是不幸的 - 我不知道它是否真的被现实生活中的人咬伤了,但这肯定是可能的。

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

    【讨论】:

    • “棘手的一点”是我没有考虑过的这种行为的绝佳理由。我认为这可以通过将每个值类型包装在引用中来解决(例如 always 为每个变量创建一个单独的类并根据需要组合它们),但是该间接层可能会降低性能,这会影响用户比终身问题更频繁。并且知情的用户可以很容易地更改他们的代码,以防止发生生命周期问题,如果他们发现它会导致问题。
    • @Brian:不只是值类型...如果变量的值发生变化(例如,字符串变量从引用“A”变为引用“B” ) 那么这也必须传播。基本上每个变量实例化都必须有一个存储位置。恶心。
    • 故意依靠延长寿命很难证明是合理的,但我可以看到有人不小心做了一些只因为它而起作用的事情,然后当改进来临时......(另外,@arootbeer,是“布偶”在其他地方是一个不寻常的贬义词,这是我经常去的地方之一)
    • @Jon Hanna:我想起了blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx,尽管后者谈到了相反的问题。
    • @Jon Hanna - 这不是我习惯听到的。不过,我不介意多听:P
    【解决方案2】:

    乔恩当然是对的。这通常导致的问题是:

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

    所以我们有一个昂贵的资源,它的生命周期现在取决于 longLife 的生命周期,尽管很早就收集了 shortLife。

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

    我想在假设的 C# 未来版本中解决它,但我不保证。显而易见的方法是根据它们被捕获的 lambdas 来识别封闭变量的等价类,并为每个等价类生成一个闭包类,而不是单个闭包类。

    我们还可以通过分析封闭变量写入的内容来做一些事情。正如 Jon 所指出的,我们目前受限于我们需要捕获变量而不是值。如果我们在创建闭包后识别出从不写入的变量,并将它们变成封闭值而不是封闭变量,我们可以在代码生成策略中更加灵活。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-10
      • 1970-01-01
      • 2012-02-07
      • 2015-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-21
      相关资源
      最近更新 更多