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