62

我看过无数关于变量捕获如何引入变量以创建闭包的帖子,但是它们似乎都没有具体的细节,而是将整个事情称为“编译器魔法”。

我正在寻找一个明确的解释:

  1. 如何实际捕获局部变量。
  2. 捕获值类型与引用类型之间的差异(如果有)。
  3. 以及是否存在与值类型相关的装箱。

我更喜欢在值和指针方面的答案(更接近内部发生的事情的核心),尽管我也会接受一个涉及值和引用的明确答案。

4

1 回答 1

82
  1. 很棘手。一分钟后就会明白。
  2. 没有区别 - 在这两种情况下,捕获的都是变量本身。
  3. 不,没有拳击发生。

通过示例演示捕获的工作原理可能是最简单的......

下面是一些使用捕获单个变量的 lambda 表达式的代码:

using System;

class Test
{
    static void Main()
    {
        Action action = CreateShowAndIncrementAction();
        action();
        action();
    }

    static Action CreateShowAndIncrementAction()
    {
        Random rng = new Random();
        int counter = rng.Next(10);
        Console.WriteLine("Initial value for counter: {0}", counter);
        return () =>
        {
            Console.WriteLine(counter);
            counter++;
        };
    }
}

现在这就是编译器为您做的事情——除了它会使用在 C# 中不会真正出现的“不可言说”的名称。

using System;

class Test
{
    static void Main()
    {
        Action action = CreateShowAndIncrementAction();
        action();
        action();
    }

    static Action CreateShowAndIncrementAction()
    {
        ActionHelper helper = new ActionHelper();        
        Random rng = new Random();
        helper.counter = rng.Next(10);
        Console.WriteLine("Initial value for counter: {0}", helper.counter);

        // Converts method group to a delegate, whose target will be a
        // reference to the instance of ActionHelper
        return helper.DoAction;
    }

    class ActionHelper
    {
        // Just for simplicity, make it public. I don't know if the
        // C# compiler really does.
        public int counter;

        public void DoAction()
        {
            Console.WriteLine(counter);
            counter++;
        }
    }
}

如果您捕获在循环中声明的变量,您最终会得到一个新的ActionHelperfor 循环的每次迭代实例 - 因此您将有效地捕获变量的不同“实例”。

当您从不同范围捕获变量时,它会变得更加复杂......让我知道您是否真的想要那种级别的详细信息,或者您可以编写一些代码,在 Reflector 中反编译并遵循它:)

注意如何:

  • 不涉及拳击
  • 不涉及指针,或任何其他不安全代码

编辑:这是两个代表共享变量的示例。一个代表显示 的当前值counter,另一个增加它:

using System;

class Program
{
    static void Main(string[] args)
    {
        var tuple = CreateShowAndIncrementActions();
        var show = tuple.Item1;
        var increment = tuple.Item2;

        show(); // Prints 0
        show(); // Still prints 0
        increment();
        show(); // Now prints 1
    }

    static Tuple<Action, Action> CreateShowAndIncrementActions()
    {
        int counter = 0;
        Action show = () => { Console.WriteLine(counter); };
        Action increment = () => { counter++; };
        return Tuple.Create(show, increment);
    }
}

...和扩展:

using System;

class Program
{
    static void Main(string[] args)
    {
        var tuple = CreateShowAndIncrementActions();
        var show = tuple.Item1;
        var increment = tuple.Item2;

        show(); // Prints 0
        show(); // Still prints 0
        increment();
        show(); // Now prints 1
    }

    static Tuple<Action, Action> CreateShowAndIncrementActions()
    {
        ActionHelper helper = new ActionHelper();
        helper.counter = 0;
        Action show = helper.Show;
        Action increment = helper.Increment;
        return Tuple.Create(show, increment);
    }

    class ActionHelper
    {
        public int counter;

        public void Show()
        {
            Console.WriteLine(counter);
        }

        public void Increment()
        {
            counter++;
        }
    }
}
于 2011-03-25T21:30:38.103 回答