21匿名方法
21.1.匿名方法表達式
匿名方法表達式(anonymous-method-expression)定義了匿名方法(anonymous method),它將計算為引用該方法的一個具體值。
l primary-no-array-creation-expression(基本非陣列建立表達式:)
…
anonymous-method-expression(匿名方法表達式)
l anonymous-method-expression:
delegate anonymoatureus-odusnsign block(匿名方法表達式: delegate 匿名方法簽署可選區塊)
l anonymous-method-signature:
( anonymous-method-parameter-list opt )(匿名方法簽署: 匿名方法參數清單可選)
l anonymous- method-parameter-list:
anonymous-method-parameter
anonymous-method-parameter-list , anonymous-method-parameter(匿名方法參數清單: 匿名方法參數匿名方法參數清單)
l anonymous-method-parameter: -modifieropt type identifier(匿名方法參數: 參數修飾語可選類型標識符)
匿名方法表達式被歸類為具有特定轉換規則(§21.3)的值。
匿名方法表達式為參數、局部變數和常數定義了一個新的宣告空間,並且為標籤(§3.3)定義了一個新的宣告空間。
如果一個匿名方法表達式具有匿名方法簽名,那麼與之相容的委託類型將被限制為那些具有相同順序(§21.3)相同參數類型和修飾符的委託類型集合。如果匿名方法表達式不具有匿名方法簽名,那麼與之相容的委託類型將被限制為那些沒有輸出參數的委託類型集合。
請注意,匿名方法簽章不能包含特性或參數陣列。不過,匿名方法簽章可以與其參數清單包含參數陣列的委託類型相容。
隱式轉換存在來自於與任何委託相容的匿名方法表達式。如果D是委託類型,而A是匿名方法表達式,那麼如果下面的條件滿足的話,D就與A相容:
l 首先,D的參數類型與A相容:
n 如果A不包含匿名方法簽名,那麼D可以有零或多個任意型別的參數,前提是D沒有任何參數具有輸出參數修飾符。
n 如果A具有匿名方法簽名,那麼D必須具有相同數量的參數,A的每個參數與D的對應參數必須具有相同的類型,並且在A上的每個參數的ref或out修飾符的存在與否,都必須與D的對應參數相符。 D的最後一個參數是否是參數數組和D與A的兼容性無關。
l 其次,D的回傳類型必須與A相容,對於這些規則,不考慮A包含任何其他匿名方法區塊的情況。
n 如果D採用void聲明傳回類型,那麼包含在A中的任何回傳語句都不應該指定表達式。
n 如果D採用型別R宣告回傳型別,那麼包含在A中的任何回傳語句的都必須指定一個可以隱含轉換(§6.1)到R的表達式。並且,A的區塊的結束點必須是不可達的。
除了到與之相容的委託類型的任何隱式轉換之外,不存在匿名方法的任何其他轉換,即便是對於object類型也是如此。
下面的例子說明了這些規則:
delegate void D(int x); D d1 = delegate { }; // Ok D d2 = delegate() { }; // 错误,签名不匹配 D d3 = delegate(long x) { }; //错误,签名不匹配 D d4 = delegate(int x) { }; // Ok D d5 = delegate(int x) { return; }; // Ok D d6 = delegate(int x) { return x; }; // 错误,返回类型不匹配 delegate void E(out int x); E e1 = delegate { }; // 错误e具有输出参数 E e2 = delegate(out int x) { x = 1; }; // Ok E e3 = delegate(ref int x) { x = 1; }; //错误,签名不匹配 delegate int P(params int[] a); P p1 = delegate { }; // 错误,块的结束点可达 P p2 = delegate { return; }; // 错误,返回类型不匹配 P p3 = delegate { return 1; }; // Ok P p4 = delegate { return "Hello"; }; //错误,返回类型不匹配 P p5 = delegate(int[] a) { // Ok return a[0]; }; P p6 = delegate(params int[] a) { // 错误, 具有params 修饰符 return a[0]; }; P p7 = delegate(int[] a) { //错误,返回类型不匹配 if (a.Length > 0) return a[0]; return "Hello"; }; delegate object Q(params int[] a); Q q1 = delegate(int[] a) { // Ok if (a.Length > 0) return a[0]; return "Hello"; };
new D(delegate { Console.WriteLine("hello"); })
等價於
(D) delegate { Console.WriteLine("hello"); }
匿名方法表达式的块遵循下列规则:
l 如果匿名方法包含签名,那么在签名中指定的参数在块内是有效的。如果匿名方法不具有签名,它可以被转换为具有参数的委托类型(§21.3),但参数在块内不可访问。
l 除了在最接近的封闭匿名方法签名中指定的ref和out参数(如果有的话)以外,对于块来说访问ref或者out参数将导致编译时错误。
l 当this的类型是一个结构类型时,对于块来说,访问this将导致编译时错误。无论该访问是显式的(像this.x)或者隐式的(像对于在结构实例的成员中的x),情况都是如此。该规则只是禁止此类访问方式,但并不影响在结构中成员查找的结果。
l 块可以访问匿名方法的外部变量(§21.5)。当匿名方法表达式被计算(§21.6)的时候,对于外部变量的访问,将会引用激活的(active)变量的实例。
l 对于块来说,包含一个其目标在块之外,或一个内嵌的匿名方法的块之内的goto语句、break语句或continue语句,将导致编译时错误。
l 在块内的return 语句,将从最接近的封闭匿名方法调用中返回控制权,而不是从封闭函数成员中返回。在return 语句中指定的表达式必须与某个委托类型兼容,而最接近的匿名方法表达式将被转换到该委托类型(§21.3)。
执行一个匿名方法的程序块,除了通过匿名方法表达式的计算和调用(evaluation and invocation)之外,是否还有其他方法,并没有明确地详细说明。特别的是,编译器可以通过合成一个或多个命名方法或类型来实现匿名方法,任何此类合成的元素的名字,必须为编译器的使用而保留在一个地方:名字必须保留两个连续下划字符。
21.5外部变量
作用域包含匿名方法表达式的任何局部变量、值参数和参数数组,都被称为匿名方法表达式的外部变量。在类的实例函数成员中,this值被认为是一个值参数,它也是包含在函数成员内的任何匿名方法表达式的外部变量
21.5.1捕获外部变量
当外部变量通过匿名方法而被引用时,就可以说这个外部变量被匿名方法所捕获(captured)了。通常,局部变量的生存期被限制为它所关联的程序块或语句的执行区(§5.1.7)。但被捕获的外部变量的生存期将至少被延长,直到引用匿名方法的委托可以被垃圾回收时为止。
示例
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
当局部变量或值参数被匿名方法所捕获时,该局部变量和值参数将不再被认为是固定的(fixed)变量(§18.3),相反它成了可移动的(movable)变量。因此,任何取得被捕获的外部变量地址的不安全代码都必须首先使用fixed语句固定该变量。
21.5.2局部变量实例化
当程序执行到变量的作用域时,局部变量就被认为是实例化(instantiated)了。例如,当下面的方法被调用时,局部变量将被三次实例化和初始化——对于循环中的每次迭代都有一次。
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; ... } }
通常,我们无法确切地看到一个局部变量多久被实例化一次——因为实例化的生命期被拆散(disjoint)了,可能的情况是,每次实例化都只是使用相同的存储位置。然而当一个匿名方法捕获一个局部变量的时候,实例化的影响将变得很明显。如示例
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
请注意在F的新版本中创建的三个委托依据相等运算符(§21.7)是等价的。并且,允许编译器(但不是必须的)将三次实例化优化为一个单一的委托实例(§21.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
21.6匿名方法计算
匿名方法表达试的运行时计算产生一个引用匿名方法的委托实例,并且被捕获的外部变量的集合(可能为空)在计算时(the time of the evaluation)是活跃的(active)。当由匿名方法表达式所产生的委托被调用时,匿名方法体就会执行。方法体内的代码将使用由该委托引用而被捕获的外部变量执行。
由匿名方法表达时产生的委托调用列表包含一个单一入口。该委托的确切目标对象和目标方法都是未指定的。需要特别的注意的是,委托的目标对象是否为null,以及封闭函数成员的this值,或其他对象都是未指定的。
语义上相同的匿名方法的计算,如果它们带具有相同被捕获的外部变量集合(可能为空),可以(但不是必须)返回相同的委托实例。术语“语义上相同”用在这里,意思是说,该匿名方法的执行期在所有情况下,都产生给定相同实参的相同效果。这条规则允许如下的代码优化。
delegate double Function(double x); class Test { static double[] Apply(double[] a, Function f) { double[] result = new double[a.Length]; for (int i = 0; i < a.Length; i++) result[i] = f(a[i]); return result; } static void F(double[] a, double[] b) { a = Apply(a, delegate(double x) { return Math.Sin(x); }); b = Apply(b, delegate(double y) { return Math.Sin(y); }); ... }
}
由于两个匿名方法委托具有被捕获外部变量的相同集合(都为空),并且由于匿名方法在语义上是相同的,所以允许编译器产生引用同一目标方法的委托。实际上,这里允许编译器从两个匿名方法表达式返回相同的委托实例。
(to be continued)
以上就是C# 2.0 Specification(匿名方法)(一)的内容,更多相关内容请关注PHP中文网(www.php.cn)!