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(匿名方法參數: 參數修飾符 可選 類型 標識符)
匿名方法表達式被歸類為具有特定轉換規則(§21.3)的值。
匿名方法表達式為參數、局部變量和常數定義了一個新的聲明空間,並且為標簽(§3.3)定義了一個新的聲明空間。
21.2匿名方法簽名
可選的匿名方法簽名(anonymous-method-signature)為該匿名方法定義了正式參數的名字和類型。匿名方法的參數作用域為塊(block)。匹配其作用域包含匿名方法表達式(anonymous-method-expression)的局部變量、局部常數或參數的名字,對於匿名方法參數的名字來說是一個編譯時錯誤。
如果一個匿名方法表達式具有匿名方法簽名,那麼與之兼容的委托類型將被限制為那些具有相同順序(§21.3)相同參數類型和修飾符的委托類型集合。如果匿名方法表達式不具有匿名方法簽名,那麼與之兼容的委托類型將被限制為那些沒有輸出參數的委托類型集合。
請注意,匿名方法簽名不能包含特性或者參數數組。不過,匿名方法簽名可以與其參數列表包含參數數組的委托類型兼容。
21.3匿名方法轉換
匿名方法表達式被歸類為一個無類型的值。匿名方法表達式可以用於委托創建表達式(§21.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中的任何返回語句的都必須指定一個可以隱式轉換(§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";
};
21.3.1委托創建表達式
委托創建表達式[delegate-creation-expression (§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); });
...
}
}
由於兩個匿名方法委托具有被捕獲外部變量的相同集合(都為空),並且由於匿名方法在語義上是相同的,所以允許編譯器產生引用同一目標方法的委托。實際上,這裡允許編譯器從兩個匿名方法表達式返回相同的委托實例。