C#中的延续

时间:2014-12-29 09:18:32

标签: c# .net lambda closures continuations

我对C#中的延续有疑问。我在Tomas Petricek和Jon Skeet的书“真实世界功能编程”中有一个例子。

void StringLengthCont(string s, Action<int> cont) {
   cont(s.Length); 
}

void AddLengthsCont() {
 StringLengthCont("One", x1 =>
    StringLengthCont("Two", x2 =>
        Console.WriteLine(x1 + x2)
   ))
}

现在这对我来说非常困惑。在这种情况下,我们有一个方法StringLengthCont,它需要一个字符串,s和一个Action<int> cont,然后调用长度为Action<int>的{​​{1}}作为一个论点。

到目前为止我明白了。但是最后一次调用s的内部lambda没有StringLengthCont的签名?在我看来,它需要两个整数,将它们加在一起并返回一个空白。

3 个答案:

答案 0 :(得分:3)

  

但是最后一次调用StringLengthCont的内部lambda并不是这样   有行动签名吗?

没有。会发生以下情况:

您第一次致电StringLengthCont,需要Action<int>。传递的实际操作(作为Lambda表达式)再次调用StringLengthCont。第二次,它通过&#34; Two&#34;作为字符串参数,并创建一个名为x2的参数,该参数作为参数传递给StringLengthCont(第二个)。因为x1x2仍然在范围内(这发生在捕获的变量和编译器处理lambda表达式的方式),我们将它们传递给Console.WriteLine以便打印添加的那些两个整数。

也许以这种方式看会更清楚:

StringLengthCont("One", new Action<int>(x1 => 
                        StringLengthCont("Two", 
                                         new Action<int>((x2) => 
                                         Console.WriteLine(x1 + x2)))));

或者也许这样:

void AddLengthsCont()
{
    StringLengthCont("One", x1 => CallStringLengthAgain(x1));
}

private void CallStringLengthAgain(int x1)
{
    StringLengthCont("Two", x2 => Console.WriteLine(x1 + x2));
}

答案 1 :(得分:1)

不,它需要一个整数。但是x1也是可见的,如果您不太确定生成了哪种代码,那么查看生成的IL代码可能会有所帮助。

在这种情况下,要了解第二个labmda如何访问x1,您需要了解闭包。

第二种方法,关闭类中的变量x1,这里是生成的IL代码:

enter image description here

你看到有一个类有x1字段,这是<AddLengthsCont>b__2方法的代码,它是你的内部lambda:

    .method assembly hidebysig instance void 
        '<AddLengthsCont>b__2'(int32 x2) cil managed
{
  // Code size       17 (0x11)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      int32 ConsoleApplication9.Program/'<>c__DisplayClass0'::x1
  IL_0006:  ldarg.1
  IL_0007:  add
  IL_0008:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000d:  nop
  IL_000e:  br.s       IL_0010
  IL_0010:  ret
} // end of method '<>c__DisplayClass0'::'<AddLengthsCont>b__2'

它的作用是获取x1字段的值,将其添加到传递的参数(x2),然后调用Console.WriteLine(int32)

这是你第一个lambda的代码:

    .method private hidebysig instance void  '<AddLengthsCont>b__1'(int32 x1) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       40 (0x28)
  .maxstack  4
  .locals init ([0] class ConsoleApplication9.Program/'<>c__DisplayClass0' 'CS$<>8__locals0')
  IL_0000:  newobj     instance void ConsoleApplication9.Program/'<>c__DisplayClass0'::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  ldarg.1
  IL_0008:  stfld      int32 ConsoleApplication9.Program/'<>c__DisplayClass0'::x1
  IL_000d:  ldarg.0
  IL_000e:  ldstr      "Two"
  IL_0013:  ldloc.0
  IL_0014:  ldftn      instance void ConsoleApplication9.Program/'<>c__DisplayClass0'::'<AddLengthsCont>b__2'(int32)
  IL_001a:  newobj     instance void class [mscorlib]System.Action`1<int32>::.ctor(object,
                                                                                   native int)
  IL_001f:  call       instance void ConsoleApplication9.Program::StringLengthCont(string,
                                                                                   class [mscorlib]System.Action`1<int32>)
  IL_0024:  nop
  IL_0025:  br.s       IL_0027
  IL_0027:  ret
} // end of method Program::'<AddLengthsCont>b__1'

此方法:

  1. 创建由编译器(IL_0000
  2. 生成的类的实例
  3. 设置x1字段(IL_0008
  4. 获取指向<AddLengthsCont>b__2方法的指针,该方法是为内部lambda(IL_0014)生成的方法
  5. 通过将Action<int>指向<AddLengthsCont>b__2IL_001a
  6. 来创建StringLengthCont
  7. 并将其传递给IL_001f方法。 ({{1}})

答案 2 :(得分:0)

对于匿名函数(例如示例中的函数),有一些特殊规则:

x2 => Console.WriteLine(x1 + x2)
  

当外部变量被匿名函数引用时,外部变量被称为已被匿名函数捕获。通常,局部变量的生命周期仅限于执行与之关联的块或语句(第5.1.7节)。但是,捕获的外部变量的生命周期至少会延长,直到从匿名函数创建的委托或表达式树符合垃圾回收的条件。

因此,上面的函数可以访问声明函数的作用域中的所有变量,并将这些变量保存在编译器生成的类中。所以匿名函数转换为这样的东西:

class __Locals1
{
    public int x1;
    public void __Method1(int x2) {
        Console.WriteLine(x1 + x2);
    }
}

__Locals1 __locals1 = new __Locals1();
__locals1.x1 = x1;  
__locals1.__Method1(x2);