ac#lambda如何捕获变量

Sum*_*ith 7 c# lambda closures

为什么以下代码打印11次?

int i = 10;
Action fn1 = () => Console.WriteLine(i);
i = 11;
Action fn2 = () => Console.WriteLine(i);
fn1();
fn2();
Run Code Online (Sandbox Code Playgroud)

产出11 11

根据这篇文章中的答案 - 如何告诉lambda函数捕获副本而不是C#中的引用? - 将lambda转换为具有捕获变量副本的类.如果是这样的话我的例子不应该打印10和11吗?

现在,当lambda通过引用捕获时它如何影响捕获的变量的生命周期.例如,假设上面的代码片段在函数中,并且Actions的范围对于变量是全局的,如下所示:

class Test
{
  Action _fn1;
  Action _fn2;

  void setActions()
  {
    int i = 10;
    _fn1 = () => Console.WriteLine(i);
    i = 11;
    _fn2 = () => Console.WriteLine(i);
  }

  static void Main()
  {
    setActions();
    _fn1();
    _fn2();
  }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,当调用操作时,变量i不会超出范围吗?那么,这些动作是否留下悬挂指针的参考?

Jon*_*eet 6

如果是这样的话我的例子不应该打印10和11吗?

不,因为你只有一个变量 - fn1捕获变量,而不是它的当前.所以像这样的方法:

static void Foo()
{
    int i = 10;
    Action fn1 = () => Console.WriteLine(i);
    i = 11;
    Action fn2 = () => Console.WriteLine(i);
    fn1();
    fn2(); 
}
Run Code Online (Sandbox Code Playgroud)

翻译成这样:

class Test
{
    class MainVariableHolder
    {
        public int i;
        public void fn1() => Console.WriteLine(i);
        public void fn2() => Console.WriteLine(i);
    }

    void Foo()
    {
        var holder = new MainVariableHolder();
        holder.i = 10;
        Action fn1 = holder.fn1;
        holder.i = 11;
        Action fn2 = holder.fn2;
        fn1();
        fn2();
    }
}
Run Code Online (Sandbox Code Playgroud)

这也回答了你的第二点:变量被"提升"到一个类中,因此只要委托是活着的,它的生命周期就会有效地扩展.


Chr*_*tos 0

事实上,生成的类的字段是捕获的变量,而不是它们的值。因此,当执行lambda时,运行时将检查当前i,即11,因为您在调用lambda之前已经更新了该值。这就是你看到这个的原因。为了验证这个论点,您可以重新排列语句的顺序,如下所示:

int i = 10;
Action fn1 = () => Console.WriteLine(i);
fn1();
i = 11;
Action fn2 = () => Console.WriteLine(i);
fn2();
Run Code Online (Sandbox Code Playgroud)