天天看點

C# 2.0 Specification(匿名方法)(一)

<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> 21 匿名方法

21.1. 匿名方法表達式

匿名方法表達式(anonymous-method-expression)定義了 匿名方法(anonymous method),它将計算為引用該方法的一個具體值。

l primary-no-array-creation-expression(基本非數組建立表達式:)

anonymous-method-expression( 匿名方法表達式)

l anonymous-method-expression:

delegate anonymous-method-signature opt 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:

parameter-modifieropt type identifier( 匿名方法參數: 參數修飾符 可選 類型 辨別符)

匿名方法表達式被歸類為具有特定轉換規則(⧲1.3)的值。

匿名方法表達式為參數、局部變量和常數定義了一個新的聲明空間,并且為标簽(⧳.3)定義了一個新的聲明空間。

21.2 匿名方法簽名

可選的 匿名方法簽名(anonymous-method-signature)為該 匿名方法定義了正式參數的名字和類型。 匿名方法的參數作用域為塊(block)。比對其作用域包含 匿名方法表達式(anonymous-method-expression)的局部變量、局部常數或參數的名字,對于 匿名方法參數的名字來說是一個編譯時錯誤。

如果一個 匿名方法表達式具有 匿名方法簽名,那麼與之相容的委托類型将被限制為那些具有相同順序(⧲1.3)相同參數類型和修飾符的委托類型集合。如果 匿名方法表達式不具有 匿名方法簽名,那麼與之相容的委托類型将被限制為那些沒有輸出參數的委托類型集合。

請注意, 匿名方法簽名不能包含特性或者參數數組。不過, 匿名方法簽名可以與其參數清單包含參數數組的委托類型相容。

21.3 匿名方法轉換

匿名方法表達式被歸類為一個無類型的值。 匿名方法表達式可以用于委托建立表達式(⧲1.3.1)中。 匿名方法表達式的所有其他合法的使用取決于在此定義的隐式轉換。

隐式轉換存在來自于與任何委托相容的 匿名方法表達式。如果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中的任何傳回語句的都必須指定一個可以隐式轉換(⧶.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";

};

21.3.1委托建立表達式

委托建立表達式[delegate-creation-expression (⧷.5.10.3)]可被用作将 匿名方法轉換到一個委托類型的替代文法。如果用作委托建立表達式的實參的表達式是一個 匿名方法表達式,那麼 匿名方法将使用上面定義的隐式轉換規則轉換到給定的委托類型。例如,如果D是一個委托類型,那麼表達式

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

等價于

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

21.4 匿名方法塊

匿名方法表達式的塊遵循下列規則:

l 如果 匿名方法包含簽名,那麼在簽名中指定的參數在塊内是有效的。如果 匿名方法不具有簽名,它可以被轉換為具有參數的委托類型(⧲1.3),但參數在塊内不可通路。

l 除了在最接近的封閉 匿名方法簽名中指定的ref和out參數(如果有的話)以外,對于塊來說通路ref或者out參數将導緻編譯時錯誤。

l 當this的類型是一個結構類型時,對于塊來說,通路this将導緻編譯時錯誤。無論該通路是顯式的(像this.x)或者隐式的(像對于在結構執行個體的成員中的x),情況都是如此。該規則隻是禁止此類通路方式,但并不影響在結構中成員查找的結果。

l 塊可以通路 匿名方法的外部變量(⧲1.5)。當 匿名方法表達式被計算(⧲1.6)的時候,對于外部變量的通路,将會引用激活的(active)變量的執行個體。

l 對于塊來說,包含一個其目标在塊之外,或一個内嵌的 匿名方法的塊之内的goto語句、break語句或continue語句,将導緻編譯時錯誤。

l 在塊内的return 語句,将從最接近的封閉 匿名方法調用中傳回控制權,而不是從封閉函數成員中傳回。在return 語句中指定的表達式必須與某個委托類型相容,而最接近的 匿名方法表達式将被轉換到該委托類型(⧲1.3)。

執行一個 匿名方法的程式塊,除了通過 匿名方法表達式的計算和調用(evaluation and invocation)之外,是否還有其他方法,并沒有明确地詳細說明。特别的是,編譯器可以通過合成一個或多個命名方法或類型來實作 匿名方法,任何此類合成的元素的名字,必須為編譯器的使用而保留在一個地方:名字必須保留兩個連續下劃字元。

21.5外部變量

作用域包含 匿名方法表達式的任何局部變量、值參數和參數數組,都被稱為 匿名方法表達式的外部變量。在類的執行個體函數成員中,this值被認為是一個值參數,它也是包含在函數成員内的任何 匿名方法表達式的外部變量

21.5.1捕獲外部變量

當外部變量通過 匿名方法而被引用時,就可以說這個外部變量被 匿名方法所捕獲(captured)了。通常,局部變量的生存期被限制為它所關聯的程式塊或語句的執行區(⧵.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)變量(⧱8.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的新版本中建立的三個委托依據相等運算符(⧲1.7)是等價的。并且,允許編譯器(但不是必須的)将三次執行個體化優化為一個單一的委托執行個體(⧲1.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(匿名方法)(一)

seover="window.status='正文--C# 2.0 Specification( 匿名方法)(一)';return true">

<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

繼續閱讀