C# 2.0仕様書(匿名方式) (1)

黄舟
黄舟オリジナル
2017-01-03 11:55:441124ブラウズ

21 匿名メソッド

21.1. 匿名メソッド式

匿名メソッド式 (anonymous-method-expression) は、メソッドを参照する特定の値として評価される匿名メソッド (匿名メソッド) を定義します。
l Primary-no-array-creation-expression (基本的な非配列作成式:)

anonymous-method-expression (匿名メソッド式)
l anonymous-method-expression:
delegate anonymous-method-signature opt ブロック(匿名メソッド式: デリゲート匿名メソッド署名オプションブロック)
l anonymous-method-signature:
( anonymous-method-parameter-list opt ) (匿名メソッド署名: 匿名メソッドパラメータリストオプション)
l anonymous-method-パラメータリスト:
匿名メソッドパラメータ
匿名メソッドパラメータリスト、匿名メソッドパラメータ (匿名メソッドパラメータリスト: 匿名メソッドパラメータ匿名メソッドパラメータリスト)
l 匿名メソッドパラメータ:
パラメータ -modifieropt型識別子 (匿名メソッドのパラメータ: パラメータ修飾子、オプションの型識別子)
匿名メソッドの式は、特定の変換規則 (§21.3) を持つ値として分類されます。
匿名メソッド式は、パラメータ、ローカル変数、定数の新しい宣言空間と、ラベルの新しい宣言空間を定義します (§3.3)。

21.2 匿名メソッド署名

オプションの匿名メソッド署名 (anonymous-method-signature) は、匿名メソッドの仮パラメータの名前と型を定義します。匿名メソッドのパラメータスコープはブロックです。スコープに匿名メソッド式が含まれるローカル変数、ローカル定数、またはパラメーターの名前と一致すると、匿名メソッドのパラメーター名のコンパイル時エラーが発生します。
匿名メソッド式に匿名メソッド シグネチャがある場合、それと互換性のあるデリゲート型は、同じパラメーター型と修飾子を同じ順序で持つデリゲート型のセットに限定されます (§21.3)。匿名メソッド式に匿名メソッド シグネチャがない場合、その式と互換性のあるデリゲート型は、出力パラメーターを持たないデリゲート型のセットに限定されます。

匿名メソッドのシグネチャには属性やパラメーター配列を含めることはできないことに注意してください。ただし、匿名メソッド シグネチャは、パラメーター リストにパラメーター配列が含まれるデリゲート型と互換性があります。

21.3 匿名メソッドの変換

匿名メソッドの式は、型なしの値として分類されます。匿名メソッド式は、デリゲート作成式で使用できます (§21.3.1)。匿名メソッド式の他のすべての正当な使用法は、ここで定義された暗黙的な変換に依存します。
あらゆるデリゲートと互換性のある匿名メソッド式からの暗黙的な変換が存在します。 D がデリゲート型で、A が匿名メソッド式の場合、次の条件が満たされていれば、D は A と互換性があります:
l まず、D のパラメータ型は A と互換性があります:
n A に匿名メソッドが含まれていない場合メソッド シグネチャの場合、D のパラメータに出力パラメータ修飾子がない限り、D は任意の型のパラメータを 0 個以上持つことができます。
n A が匿名メソッド シグネチャを持つ場合、D は同じ数のパラメータを持たなければならず、A の各パラメータは D の対応するパラメータと同じ型でなければならず、各パラメータの ref または out 修飾子が存在する必要があります。 A 一致するかどうかに関係なく、D の対応するパラメータと一致する必要があります。 D の最後のパラメータがパラメータ配列であるかどうかは、D と A の互換性とは何の関係もありません。
l 次に、D の戻り値の型は A と互換性がある必要があります。これらのルールでは、A に他の匿名メソッド ブロックが含まれる場合は考慮されません。
n D が戻り値の型 void を宣言する場合、A に含まれる return ステートメントでは式を指定してはなりません。
n D が型 R の戻り値の型を宣言する場合、A に含まれる return ステートメントは暗黙的に R に変換 (§6.1) できる式を指定する必要があります。また、A のブロックの終点は到達不可能でなければなりません。
互換性のあるデリゲート型への暗黙的な変換以外に、オブジェクト型であっても匿名メソッドに対する他の変換はありません。
次の例は、これらのルールを示しています:

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";
};

21.3.1 デリゲート作成式

デリゲート作成式 (§7.5.10.3)] は、匿名メソッドをデリゲートに変換するために使用できます。型の代替構文。デリゲート作成式への引数として使用される式が匿名メソッド式である場合、その匿名メソッドは、上で定義した暗黙的な変換規則を使用して、指定されたデリゲート型に変換されます。たとえば、D がデリゲート型の場合、式

new D(delegate { Console.WriteLine("hello"); })


(D) delegate { Console.WriteLine("hello"); }

21.4 匿名メソッド ブロック

と同等です。

匿名方法表达式的块遵循下列规则:
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)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。