我见过无数关于变量捕获如何引入变量以创建闭包的文章,但是它们似乎都缺乏具体细节,并称其为“编译器魔术”。
我正在寻找以下方面的明确解释:
我更倾向于根据值和指针(更接近内部发生的事情)回答问题,尽管我也会接受涉及值和引用的明确答案。
通过示例演示捕获的工作方式可能是最简单的…
这是一些使用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对于循环的每次迭代,您都会得到一个新的实例-这样您就可以有效地捕获变量的不同“实例”。
ActionHelper
当您捕获来自不同作用域的变量时,它会变得更加复杂…让我知道您是否真的想要那种详细程度,或者您可以编写一些代码,在Reflector中反编译然后按照以下步骤进行:)
注意如何:
编辑:这是两个代表共享变量的示例。一个代表显示的当前值counter,另一个代表增加它的值:
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++; } } }