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

C# 闭包中变量捕获的详细说明,c#,.net,closures,value-type,reference-type,C#,.net,Closures,Value Type,Reference Type,我已经看过无数关于变量捕获如何为闭包的创建引入变量的帖子,但是它们似乎都没有具体的细节,并称整个事情为编译器魔术 我正在寻找一个明确的解释: 如何实际捕获局部变量。 捕获值类型与引用类型之间的差异(如果有)。 以及是否存在与值类型相关的装箱。 我倾向于用更接近内部发生的核心的值和指针来回答,尽管我也会接受一个包含值和引用的明确答案 这很棘手。马上就来。 没有区别-在这两种情况下,捕获的是变量本身。 不,没有拳击比赛。 通过一个例子来演示捕获是如何工作的可能是最简单的 下面是一些使用lambda表

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

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

如何实际捕获局部变量。 捕获值类型与引用类型之间的差异(如果有)。 以及是否存在与值类型相关的装箱。 我倾向于用更接近内部发生的核心的值和指针来回答,尽管我也会接受一个包含值和引用的明确答案

这很棘手。马上就来。 没有区别-在这两种情况下,捕获的是变量本身。 不,没有拳击比赛。 通过一个例子来演示捕获是如何工作的可能是最简单的

下面是一些使用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中进行反编译,然后按照以下步骤进行操作:

请注意:

没有拳击比赛 没有涉及指针或任何其他不安全代码 编辑:下面是两个代理共享一个变量的示例。一个委托显示计数器的当前值,另一个委托将其递增:

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++;
        }
    }
}

你读过文档了吗?是什么让你认为指针牵涉其中?请记住,这是在C级本身完成的,而不是由CLR完成的。后台引用是指针。我在寻找这种幕后解释,除非它能让事情更清楚地理解。幕后引用是一些当前实现的指针,即使在其中,也不能保证它们会保持这种方式。FPGA的.Net解释器或编译器可以执行其他操作,并且仍然可以运行所有有效的非不安全代码,而不需要任何指针概念。@DuckMaestro:VirtualBlackBox完全正确。指针的实现与C语言规范提供的保证无关。在理解功能时,一定要保持适当的思维水平,而闭包完全可以理解,而不必考虑虚拟机到底在做什么或做什么。@Jon你说捕获是变量而不是值。这意味着,我猜,如果在同一方法中声明的两个lambda引用同一个变量,那么它们都捕获同一个变量。如果其中一个lambda修改了该变量,那么另一个lambda会看到该变量持有的修改值。还是我偏离了目标?@大卫:是的,完全正确。在这种情况下,生成的类中将有两个实例方法,并且两个委托将引用同一个目标实例。@Jon谢谢。我实际上不懂任何C语言,我的大部分时间都花在Delphi上。Delphi等效项的行为方式相同。它通常不会出现在大多数用例中,但我认为大多数人天真的期望是捕获值。在这一特定的误会上,你可以走很长的路。@David:是的,你可以。特别是在Java中,匿名类就是这样工作的:@JonSkeet,当捕获的变量之一是类字段而另一个不是时会发生什么?匿名方法是否指向该实例的字段?这个实例是否以某种方式传递给生成的类?还有一个问题——编译器是否可能不知道哪些匿名方法共享一些捕获的变量?