我对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
的签名?在我看来,它需要两个整数,将它们加在一起并返回一个空白。
答案 0 :(得分:3)
但是最后一次调用StringLengthCont的内部lambda并不是这样 有行动签名吗?
没有。会发生以下情况:
您第一次致电StringLengthCont
,需要Action<int>
。传递的实际操作(作为Lambda表达式)再次调用StringLengthCont
。第二次,它通过&#34; Two&#34;作为字符串参数,并创建一个名为x2
的参数,该参数作为参数传递给StringLengthCont
(第二个)。因为x1
和x2
仍然在范围内(这发生在捕获的变量和编译器处理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
代码:
你看到有一个类有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'
此方法:
IL_0000
)x1
字段(IL_0008
)<AddLengthsCont>b__2
方法的指针,该方法是为内部lambda(IL_0014
)生成的方法Action<int>
指向<AddLengthsCont>b__2
(IL_001a
)StringLengthCont
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);