程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C#2.0語言規范(三)匿名方法

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

編輯:關於C語言
[內容]

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);
}
}
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved