闭包中变量捕获的详细说明

时间:2011-03-25 21:27:32

标签: c# .net closures value-type reference-type

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

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

  1. 如何实际捕获局部变量。
  2. 捕获值类型与引用类型之间的差异(如果有)。
  3. 是否存在与值类型相关的任何装箱。
  4. 我的偏好是关于价值和指针的答案(更接近内部发生的事情的核心),尽管我会接受一个涉及价值观和参考的明确答案。

1 个答案:

答案 0 :(得分:77)

  1. 很棘手。将在一分钟内发布。
  2. 没有区别 - 在这两种情况下,它都是被捕获的变量本身。
  3. 不,没有发生拳击。
  4. 最简单的方法是通过示例演示捕捉的工作原理......

    这是使用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++;
            }
        }
    }
    

    如果你捕获循环中声明的变量,那么对于循环的每次迭代,你最终会得到ActionHelper的新实例 - 所以你可以有效地捕获变量的不同“实例”。

    当您从不同的范围捕获变量时,它变得更加复杂...如果您真的想要那种详细程度,请告诉我,或者您可以编写一些代码,在Reflector中对其进行反编译并遵循它:)< / p>

    请注意:

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

    编辑:这是两个代表共享变量的示例。一个代表显示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++;
            }
        }
    }