c#lambda如何捕获变量

时间:2017-06-12 09:31:59

标签: c# lambda closures

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

int i = 10;
Action fn1 = () => Console.WriteLine(i);
i = 11;
Action fn2 = () => Console.WriteLine(i);
fn1();
fn2();

输出 11 11

根据这篇文章中的答案 - How to tell a lambda function to capture a copy instead of a reference in C#? - 将lambda翻译成一个带有捕获变量副本的类。如果是这样,我的例子不应该打印10& 11?

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

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();
  }
}

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

3 个答案:

答案 0 :(得分:6)

  

如果是这种情况,我的例子不应该打印10& 11?

不,因为您只有一个变量 - fn1会捕获变量,而不是当前的。所以这样的方法:

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

翻译成这样:

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();
    }
}

这也回答了你的第二点:变量被“提升”到一个类中,所以只要委托还活着,它的生命周期就会有效地延长。

答案 1 :(得分:0)

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

int i = 10;
Action fn1 = () => Console.WriteLine(i);
fn1();
i = 11;
Action fn2 = () => Console.WriteLine(i);
fn2();

答案 2 :(得分:-1)

由于延迟执行,它打印两次,而不是因为它捕获变量的方式。

当执行被推迟时,它将打印i的最新值,因为我是被捕获的,而不是它的值。