匿名方法块
匿名方法表达式(anonymous-method-expression)的块(block)遵从下列规则:
如果匿名方法包含一个签名,则签名中指定的参数在块(block)中是可用的。如果匿名方法不包含签名,则它可以转换为一个带有参数的委托类型(见第二页),但这些参数在块(block)中无法访问。
除非在最贴切的匿名方法的签名(如果有的话)中指定了ref或out参数,否则在块中访问ref或out参数会发生编译期间错误。
当this的类型是一个结构类型时,当在块(block)中访问this是一个编译错误,不论这种能够访问是显式的(如this.x)还是隐式的(如x,而x是该结构的一个实例方法)。这一规则简单地禁止了这种访问,从而不会对结构的成员的查找结果产生影响。
块(block)可以访问匿名方法外部的变量(见3.5)。对外部变量的访问将会引用到变量的实例,该变量在匿名方法表达式(anonymous-method-expression)求值的过程中应当是活动的(见3.6)。
如果块(block)中包含的goto语句、break语句或continue语句的目标在块(block)的外面或在块(block)中包含的一个匿名方法中,则会产生编译错误。
块(block)中的return语句将控制从最近的一个匿名方法的调用中返回,而不是从函数成员中返回。return语句中指定的表达式必须和匿名方法表达式(anonymous-method-expression)转换(见3.3)得到的委托类型相匹配。
除了计算和调用匿名方法表达式(anonymous-method-expression)外,对于块(block)的执行方式没有任何明确的限制。特别地,编译器会选择通过合成个或多个命名了的方法或类型来实现一个匿名方法。这些合成的名字必须保留在编译器所使用的空间中:这些名字必须包含两个连续的下划线。
外部变量
若一个匿名方法表达式(anonymous-method-expression)包含在任何局部变量、值参数或参数数组的作用域中,则称它们(译注:指那些局部变量、值参数或参数数组)为该匿名方法表达式(anonymous-method-expression)的外部变量(outer variable)。在一个类的实例函数成员中,this被认为是一个值参数,并且是该函数成员中所包含的任何匿名方法表达式(anonymous-method-expression)的外部变量。
捕获外部变量
当在一个匿名方法中引用一个外部变量时,称该外部变量被匿名方法所捕获。通常,一个局部变量的生存期是块或与之关联的语句的执行的结束点。然而,一个被捕获的外部变量的生存期将持续到引用了匿名方法的委托符合垃圾收集的条件时。
在下面的例子中:
using System;
delegate int D();
class Test {
static D F() {
int x = 0;
D result = delegate { return ++x; }
return result;
}
static void Main() {
D d = F();
Console.WriteLine(d());
Console.WriteLine(d());
Console.WriteLine(d());
}
}
局部变量x被匿名方法所捕获,x的生存期至少被延续到从F所返回的委托符合垃圾收集条件时(这并不一定发生在程序的最末尾)。由于每个匿名方法的调用均操作了x的同一个实例,这个例子的输出将会是:
1
2
3
当一个局部变量或一个值参数被一个匿名方法所捕获,该局部变量获知参数将不再被认为是一个固定变量,而是被认为是一个可移动的变量。因此,任何unsafe代码如果记录了该外部变量的地址,则必须首先使用fixed语句来固定这个变量。
局部变量的实例化
当程序执行到一个变量的作用域中时,则该局部变量被实例化。例如,当下面的方法被调用时,局部变量x被实例化和初始化三次——每当循环迭代一次时。
static void F() {
for(int i = 0; i < 3; i++) {
int x = i * 2 + 1;
...
}
}
然而,将x的声明移到循环外面则只会引起x的一次实例化:
static void F() {
int x;
for(int i = 0; i < 3; i++) {
x = i * 2 + 1;
...
}
}
通常,没有办法观察到一个局部变量被实例化过多少次——因为实例化的生存期是脱节的,而上每次实例化都简单地使用相同的存贮空间是可能的。然而,当一个匿名方法捕获了一个局部变量,实例化的效果就明显了。下面的例子
using System;
delegate void D();
class Test {
static D[] F() {
D[] result = new D[3];
for(int i = 0; i < 3; i++) {
int x = i * 2 + 1;
result[i] = delegate { Console.WriteLine(x); };
}
return result;
}
static void Main() {
foreach (D d in F()) d();
}
}
的输出为:
1
3
5
然而,当x的声明被移到循环外面时:
static D[] F() {
D[] result = new D[3];
int x;
for(int i = 0; i < 3; i++) {
x = i * 2 + 1;
result[i] = delegate { Console.WriteLine(x); };
}
return result;
}
结果为:
5
5
5
注意,根据判等操作(见3.7),由上面这个版本的F方法所建立的三个委托是相等的。另外,编译器可以(但不是必须)将这三个实例优化为一个单独的委托实例(见3.6)。
匿名方法委托可以共享一些捕获的变量,而具有另一些捕获变量的单独的实例。例如,如果F变为
static D[] F() {
D[] result = new D[3];
int x = 0;
for(int i = 0; i < 3; i++) {
int y = 0;
result[i] = delegate { Console.WriteLine("{0} {1}", ++x, ++y); };
}
return result;
}
这三个委托捕获了x的相同的(一个)实例,而捕获y的单独的实例。输出为:
1 1
2 1
3 1
单独的匿名方法可以捕获一个外部变量的相同的实例。下面的例子中:
using System;
delegate void Setter(int value);
delegate int Getter();
class Test {
static void Main() {
int x = 0;
Setter s = delegate(int value) { x = value; };
Getter g = delegate { return x; };
s(5);
Console.WriteLine(g());
s(10);
Console.WriteLine(g());
}
}
两个匿名方法捕获了局部变量x的相同的实例,它们可以通过改变量进行“通信”。这个例子的输出为:
5
10