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 { }; // OkD d2 = delegate() { }; // 錯誤,簽名不匹配D d3 = delegate(long x) { }; //錯誤,簽名不匹配D d4 = delegate(int x) { }; // OkD d5 = delegate(int x) { return; }; // OkD d6 = delegate(int x) { return x; }; // 錯誤,傳回型別不匹配delegate void E(out int x);E e1 = delegate { }; // 錯誤e具有輸出參數E e2 = delegate(out int x) { x = 1; }; // OkE 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; }; // OkP p4 = delegate { return "Hello"; }; //錯誤,傳回型別不匹配P p5 = delegate(int[] a) { // Okreturn 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) { // Okif (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的相同執行個體上進行操作,該樣本輸出的結果為:
123
當局部變數或值參數被匿名方法所捕獲時,該局部變數和值參數將不再被認為是固定的(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();}}
產生如下輸出。
135
但如果將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;}
其輸出如下。
555
請注意在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 12 13 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的同一執行個體,並且它們可以通過該變數“通訊”。該樣本輸出如下。
510
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(匿名方法)(一)的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!