我曾经看过无数关于变量捕获的帖子,这些帖子都提到了创建闭包时会引入变量,但是它们似乎都没有详细解释,而只是称整个过程为“编译器的魔法”。
我正在寻找一个清晰明了的解释:
- 局部变量是如何被实际捕获的。
- 在捕获值类型和引用类型之间的区别(如果有的话)。
- 是否存在任何与值类型相关的装箱操作。
我更喜欢以值和指针的方式回答(更接近内部发生的情况),但是如果有明确的答案涉及值和引用,我也会接受。
我曾经看过无数关于变量捕获的帖子,这些帖子都提到了创建闭包时会引入变量,但是它们似乎都没有详细解释,而只是称整个过程为“编译器的魔法”。
我正在寻找一个清晰明了的解释:
我更喜欢以值和指针的方式回答(更接近内部发生的情况),但是如果有明确的答案涉及值和引用,我也会接受。
最好通过一个例子来演示捕获如何工作...
这是使用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
实例,因此您实际上会捕获不同版本的变量。 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++;
}
}
}