C#2.0語言規範(三)匿名方法

來源:互聯網
上載者:User
規範 第三章 匿名方法
原著:Microsoft Corporation
原文:http://msdn.microsoft.com/vcsharp/team/language/default.aspx (SpecificationVer2.doc)
翻譯:lover_P
出處:


--------------------------------------------------------------------------------

[內容]

3.1 匿名方法運算式
3.2 匿名方法簽名
3.3 匿名方法轉換
3.3.1 委託建立運算式
3.4 匿名方法塊
3.5 外部變數
3.5.1 捕獲外部變數
3.5.2 局部變數的執行個體化
3.6 匿名方法求值
3.7 委託執行個體相等性
3.8 明確賦值
3.9 方法群組轉換
3.10 實現執行個體


3.1 匿名方法運算式
匿名方法表達(anonymous-method-expression)式定義了匿名方法(anonymous method),並求得一個引用了該方法的特殊的值。

primary-no-array-creation-expression:

anonymous-method-expression
anonymous-method-expression:
delegate anonymous-method-signatureopt block

anonymous-method-signature:
( anonymous-method-parameter-listopt )

anonymous-method-parameter-list:
anonymous-method-parameter
anonymous-method-parameter-list , anonymous-method-parameter

anonymous-method-parameter:
parameter-modifieropt type identifier

初等非數組運算式:
...
匿名方法運算式

匿名方法運算式:
delegate 匿名方法簽名可選 塊

匿名方法簽名:
( 匿名方法參數列表可選 )

匿名方法參數列表:
匿名方法參數
匿名方法參數 , 匿名方法參數

匿名方法參數:
參數修飾符可選 類型 標識符


匿名方法表達(anonymous-method-expression)是一個遵從特殊轉換規則(見3.3)的值。這個值沒有類型,但可以隱式地轉換為一個相容的委託類型。

匿名方法運算式(anonymous-method-expression)為參數、局部變數和常量以及標籤定義了一個新的聲明空間。

3.2 匿名方法簽名
可選的匿名方法簽名(anonymous-method-signature)為匿名方法的形式參數定義了名字和類型。這些參數的作用於是匿名方法的塊(block)。如果一個局部變數的範圍包含了匿名方法運算式(anonymous-method-expression),且該匿名方法的參數和該局部變數相同,則會產生編譯錯誤。

如果一個匿名方法運算式(anonymous-method-expression)具有匿名方法簽名(anonymous-method-signature),則與之相容的委託類型被強制具有相同的參數類型和修飾符,且具有相同順序(見3.3)。如果一個匿名方法運算式(anonymous-method-expression)沒有匿名方法簽名(anonymous-method-signature),則與之相相容的委託類型被強制要求沒有out參數。

注意匿名方法簽名(anonymous-method-signature)不能包含特性或參數數組(譯註:用於實現變長參數列表)。然而,一個匿名方法簽名(anonymous-method-signature)可以和一個包含參數數組的委託類型相相容。

3.3 匿名方法轉換
匿名方法運算式(anonymous-method-expression)是一個沒有類型的特殊值。一個匿名方法運算式(anonymous-method-expression)可以用於委託建立運算式(delegate-creation-expression)(見3.3.1)。對於匿名方法運算式(anonymous-method-expression)的其他有效應用取決於定義於其上的隱式轉換。

匿名方法運算式(anonymous-method-expressio)與任何相容的委託類型之間均存在隱式轉換。如果D是一個委託類型,而A是一個匿名方法運算式(anonymous-method-expression),若且唯若以下兩個條件成立的時候D和A是相容的。

首先,D的參數類型必須與A相容:
如果A不含匿名方法簽名(anonymous-method-signature),則D可以具有任意類型的零或多個參數,但這些參數不能帶有out修飾符。

如果具有匿名方法簽名(anonymous-method-signature),則D必須具有和A形同數量的參數,A中的每個參數必須和D中相應的參數具有相同的類型,並且A中每個參數上的ref或out修飾符的出現與否必須與D中的相應參數相同。如果D中的最後一個參數是參數數組,則沒有相互相容的A和D。

其次,D的傳回值類型必須與A相容。由於這一規則,A中不能包含其他匿名方法的塊(block)。
如果D的傳回值類型被聲明為void,則A中包含的所有return語句不能指定運算式。

如果D的傳回值類型被聲明為R,則A中包含的所有return語句不許指定一個能夠隱式轉換為R的運算式。A中的塊(block)的終點必須可達。

除了和相相容的委託類型之間的隱式轉換,匿名方法運算式(anonymous-method-expression)與任何類型之間不存在任何轉換,包括object類型。

下面的例子詳細地解釋了這些規則:

delegate void D(int x);

D d1 = delegate { }; // 正確
D d2 = delegate() { }; // 錯誤,簽名不匹配
D d3 = delegate(long x) { }; // 錯誤,簽名不匹配
D d4 = delegate(int x) { }; // 正確
D d5 = delegate(int x) { return; }; // 正確
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; }; // 正確
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; }; // 正確
P p4 = delegate { return "Hello"; }; // 錯誤,傳回值類型不符

P p5 = delegate(int[] a) { // 正確
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) { // 正確
if(a.Length > 0) return a[0];
return "Hello";
};

3.3.1 委託建立運算式
委託建立運算式(delegate-creation-expression)可以用於匿名方法和委託類型之間的轉換文法的替代品。如果一個委託建立運算式(delegate-creation-expression)的參數運算式(expression)是一個匿名方法運算式(anonymous-method-expression),則匿名方法依照上面定義的隱式轉換規則轉換為給定的委託類型。例如,如果D是一個委託類型,則運算式

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

等價於運算式

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

3.4 匿名方法塊
匿名方法運算式(anonymous-method-expression)的塊(block)遵從下列規則:

如果匿名方法包含一個簽名,則簽名中指定的參數在塊(block)中是可用的。如果匿名方法不包含簽名,則它可以轉換為一個帶有參數的委託類型(見3.3),但這些參數在塊(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)的執行方式沒有任何明確的限制。特別地,編譯器會選擇通過合成個或多個命名了的方法或類型來實現一個匿名方法。這些合成的名字必須保留在編譯器所使用的空間中:這些名字必須包含兩個連續的底線。

3.5 外部變數
若一個匿名方法運算式(anonymous-method-expression)包含在任何局部變數、值參數或參數數組的範圍中,則稱它們(譯註:指那些局部變數、值參數或參數數組)為該匿名方法運算式(anonymous-method-expression)的外部變數(outer variable)。在一個類的執行個體函數成員中,this被認為是一個值參數,並且是該函數成員中所包含的任何匿名方法運算式(anonymous-method-expression)的外部變數。

3.5.1 捕獲外部變數
當在一個匿名方法中引用一個外部變數時,稱該外部變數被匿名方法所捕獲。通常,一個局部變數的生存期是塊或與之關聯的語句的執行的結束點。然而,一個被捕獲的外部變數的生存期將持續到引用了匿名方法的委託符合垃圾收集的條件時。

在下面的例子中:

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語句來固定這個變數。

3.5.2 局部變數的執行個體化
當程式執行到一個變數的範圍中時,則該局部變數被執行個體化。例如,當下面的方法被調用時,局部變數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

3.6 匿名方法求值
運行時對匿名方法運算式的求值將得到一個委託執行個體,該委託執行個體引用了這個匿名方法和一組活動的外部變數的集合(可能為空白)。當一個由匿名方法運算式求得的委託被調用時,匿名方法體將被執行。方法體中的代碼可以使用由委託所引用的一組捕獲了的外部變數。

有匿名方法運算式得到的委託的調用鏈表包含一個唯一的進入點。委託的確切的目標對象和目標方法是未定義的。尤其是委託的目標對象是否為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); });
...
}
}

由於兩個匿名方法委託具有相同的一組捕獲的外部變數(為空白),而且匿名方法在語義上是一致的,編譯器可以令這兩個委託引用一個相同的目標方法。甚至對於這兩個匿名方法運算式,編譯器可以返回完全一樣的委託執行個體。

3.7 委託執行個體相等性
下面的規則決定著匿名方法委託執行個體的判等操作符和Object.Equals方法的結果:

從語義上相同的帶有相同一組(也可能是都沒有沒有)被捕獲變數的匿名方法運算式(anonymous-method-expressions)所產生的委託執行個體可以(但不是必須)相等。
從語義上相同的單被捕獲變數不同的的匿名方法運算式(anonymous-method-expressions)所產生的委託執行個體必須不相等。
3.8 明確賦值
對匿名方法參數的明確賦值規則和命名方法(named method,區別於匿名方法)參數的明確複製規定一樣。也就是說,引用參數和值參數必須同國明確的賦值進行初始化,而輸出參數可以不比進行明確賦值。

另外,如果要在匿名方法正常返回之前使用輸出參數,則輸出參數必須被明確賦值。

當控制轉移到匿名方法運算式的塊中時,對於一個外部變數v的明確賦值規定與匿名方法運算式之前對v的明確賦值規定一樣。

對於一個匿名方法後面的變數v的明確賦值規定和匿名方法運算式之前的明確賦值規定相同。

下面的例子:

delegate bool Filter(int i);

void F() {
int max;

// 錯誤,max沒有被明確賦值
Filter f = delegate(int n) { return n < max; }
max = 5;
DoWork(f);
}

會產生一個編譯錯誤,因為在匿名方法聲明之前max沒有被明確賦值。下面的例子

delegate void D();

void F() {
int n;
D d = delegate { n = 1; };
d();

// 錯誤,n沒有被明確賦值
Console.WriteLine(n);
}


同樣會產生變異錯誤,因為匿名方法中對n的賦值不會影響匿名方法外部對n的明確賦值。

3.9 方法群組轉換
和3.3節中描述的匿名方法隱式轉換類似,從一個方法組到一個相容的委託類型之間也存在一個隱式的轉換。

對於一個給定的方法組E和一個委託類型D,如果允許形為new D(E)的委託建立運算式(見2.9.6),則存在E到D的隱式轉換,

下面的例子:

using System;
using System.Windows.Forms;

class AlertDialog {
Label message = new Label();
Button okButton = new Button();
Button cancelButton = new Button();`

public AlertDialog() {
okButton.Click += new EventHandler(OkClick);
cancelButton.Click += new EventHandler(CancelClick);
...
}

void OkClick(object sender, EventArgs e) {
...
}

void CancelClick(object sender, EventArgs e) {
...
}
}

構造器使用兩個new運算子建立了兩個委託執行個體。隱式方法群組轉換允許將其寫作更短的形式:

public AlertDialog() {
okButton.Click += OkClick;
cancelButton.Click += CancelClick;
...
}

與其它隱式和顯式轉換相同,轉換運算子可以顯式地用於一個特定的轉換中。因此,下面的例子:

object obj = new EventHandler(myDialog.OkClick);

可以寫作:

object obj = (EventHandler)myDialog.OkClick;

方法組和匿名方法運算式會影響到重載抉擇,但不會參與類型推斷。更多細節參見2.6.4節

3.10 實現執行個體
這一節將討論一些標準C#構造中匿名方法的可能的實現。這裡描述的實現和Visual C#編譯器基於相同的原理,但這並不是必須的實現,而只是一種可能。

本節的最後將給出包含了不同特徵的匿名方法的代碼執行個體。對於每個例子,轉換得到的相應代碼僅使用了標準C#提供的構造。這些例子中,假設標識符D為下面這樣的委託類型:

public delegate void D();

最簡單的匿名方法是不捕獲任何外部變數的匿名方法:

class Test {
static void F() {
D d = delegate { Console.WriteLine("test"); };
}
}

它會被翻譯為一個委託執行個體,它引用了一個編譯器產生的靜態方法,這個方法中包含了匿名方法中的代碼:

class Test {
static void F() {
D d = new D(__Method1);
}

static void __Method1() {
Console.WriteLine("test");
}
}

In the following example, the anonymous method references instance members of this:

下面的例子中,你名方法引用了this的執行個體成員:

class Test {
int x;

void F() {
D d = delegate { Console.WriteLine(x); };
}
}

This can be translated to a compiler generated instance method containing the code of the anonymous method:

它會被翻譯為一個編譯器產生的執行個體方法,該方法包含了匿名方法中的代碼:

class Test {
int x;

void F() {
D d = new D(__Method1);
}

void __Method1() {
Console.WriteLine(x);
}
}

In this example, the anonymous method captures a local variable:

在這個例子中,匿名方法捕獲了一個局部變數:

class Test {
void F() {
int y = 123;
D d = delegate { Console.WriteLine(y); };
}
}

The lifetime of the local variable must now be extended to at least the lifetime of the anonymous method delegate. This can be achieved by “lifting” the local variable into a field of a compiler generated class. Instantiation of the local variable (§21.5.2) then corresponds to creating an instance of the compiler generated class, and accessing the local variable corresponds to accessing a field in the instance of the compiler generated class. Furthermore, the anonymous method becomes an instance method of the compiler generated class:

局部變數的生存期現在必須擴充為至少持續到匿名方法委託的生存期結束。這可以通過將局部變數“提升”到一個編譯器產生的類的域中來完成。局部變數的執行個體化(見3.5.2)相當於建立編譯器產生的類的一個執行個體,而訪問局部變數相當於訪問編譯器建立的類的這個執行個體的域。另外,匿名方法變成編譯器產生的類的一個執行個體方法:

class Test {
void F() {
__locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}

class __Locals1 {
public int y;
public void __Method1() {
Console.WriteLine(y);
}
}
}

Finally, the following anonymous method captures this as well as two local variables with different lifetimes:

最後,下面的匿名方法捕獲了this和兩個具有不同生存期的局部變數:

class Test {
int x;

void F() {
int y = 123;

for (int i = 0; i < 10; i++) {
int z = i * 2;
D d = delegate { Console.WriteLine(x + y + z); };
}
}
}

Here, a compiler generated class is created for each statement block in which locals are captured such that the locals in the different blocks can have independent lifetimes. An instance of __Locals2, the compiler generated class for the inner statement block, contains the local variable z and a field that references an instance of __Locals1. An instance of __Locals1, the compiler generated class for the outer statement block, contains the local variable y and a field that references this of the enclosing function member. With these data structures it is possible to reach all captured outer variables through an instance of __Local2, and the code of the anonymous method can thus be implemented as an instance method of that class.

這裡,編譯器為每一個捕獲了局部變數的語句塊分別都產生了一個類。因此,那些在不同的塊中所捕獲的局部變數具有獨立的生存期。編譯器為內層語句塊建立的類__Locals2的一個執行個體,包含了局部變數z和一個引用了__Locals1的執行個體的域。編譯器為外層語句塊建立的類__Locals1的一個執行個體,包含了局部變數y和一個引用了函數成員所在類的this的一個域。通過這些資料結構,可以通過__Locals2的一個執行個體來獲得所有捕獲的外部變數,而匿名方法中的代碼因此被實現為該類的一個執行個體方法。

class Test {
void F() {
__locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;

for (int i = 0; i < 10; i++) {
__locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}

class __Locals1 {
public Test __this;
public int y;
}

class __Locals2 {
public __Locals1 __locals1;
public int z;

public void __Method1() {
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}


相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.