代码之家  ›  专栏  ›  技术社区  ›  Sasha

如何在.NET中实现匿名方法中捕获的值

  •  4
  • Sasha  · 技术社区  · 15 年前

    我对.NET的实际实现及其背后的决定感到好奇。

    例如,在Java中,所有在匿名类中使用的捕获值都必须是最终的。此要求似乎已在.NET中删除。

    此外,值类型的捕获值实现与引用类型的捕获值实现是否存在差异?

    谢谢

    1 回复  |  直到 15 年前
        1
  •  9
  •   Jon Skeet    15 年前

    找出它是如何实现的最简单的方法就是尝试它。编写一些使用捕获变量的代码,编译它,然后在 Reflector . 注意,这是 变量 它被捕获,而不是 价值 . 这是Java和C语言在这一领域的最大区别之一。

    基本思想是,包含至少一个捕获变量的每个作用域级别都会生成一个新类,其中包含已捕获变量的字段。如果有多个级别,那么内部作用域也有下一个作用域的字段,以此类推。堆栈上真正的局部变量最终成为对自动生成类实例的引用。

    下面是一个例子:

    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            List<Action> actions = new List<Action>();
    
            for (int i=0; i < 5; i++)
            {
                int copyOfI = i;
    
                for (int j=0; j < 5; j++)
                {
                    int copyOfJ = j;
    
                    actions.Add(delegate
                    {
                        Console.WriteLine("{0} {1}", copyOfI, copyOfJ);
                    });
                }
            }
    
            foreach (Action action in actions)
            {
                action();
            }        
        }
    }
    

    (如果你不抄一本课程,你会得到不同的结果-实验!)它被编译成如下代码:

    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            List<Action> actions = new List<Action>();
    
            for (int i=0; i < 5; i++)
            {
                OuterScope outer = new OuterScope();
                outer.copyOfI = i;
    
                for (int j=0; j < 5; j++)
                {
                    InnerScope inner = new InnerScope();
                    inner.outer = outer;
                    inner.copyOfJ = j;
    
                    actions.Add(inner.Action);
                }
            }
    
            foreach (Action action in actions)
            {
                action();
            }        
        }
    
        class OuterScope
        {
            public int copyOfI;
        }
    
        class InnerScope
        {
            public int copyOfJ;
            public OuterScope outer;
    
            public void Action()
            {
                Console.WriteLine("{0} {1}", outer.copyOfI, copyOfJ);
            }
        }
    }
    

    每个 对捕获变量的引用最终会经过生成类的实例,因此它不仅仅是一次性的副本。(好的,在本例中,代码中没有其他内容使用捕获的变量,但您可以很容易地想象它可以使用这些变量。)注意,对于外部循环的任何一次迭代,五个新实例都共享 OuterScope . 您可能希望尝试在委托中使用额外的代码,以查看这对事情的影响-如果委托更改 copyofI 这种变化将在下一个委托中看到;对 copyOfJ 不会显示,因为下一个委托将使用 InnerScope .