C#6.0語言規範(十五) 委托
委托啟用其他語言(如C ++,Pascal和Modula)已使用函數指針進行尋址的方案。但是,與C ++函數指針不同,委托是完全面向對象的,與成員函數的C ++指針不同,委托封裝了對象實例和方法。
委托聲明定義了從類派生的類System.Delegate
。委托實例封裝了一個調用列表,該列表是一個或多個方法的列表,每個方法都被稱為可調用實體。對於實例方法,可調用實體由該實例上的實例和方法組成。對於靜態方法,可調用實體僅包含一個方法。使用適當的參數集調用委托實例會導致使用給定的參數集調用每個委托的可調用實體。
委托實例的一個有趣且有用的屬性是它不知道或不關心它封裝的方法的類; 重要的是這些方法
委托聲明
一個delegate_declaration是type_declaration(類型聲明,聲明一個新的委托類型)。
1 delegate_declaration 2 : attributes? delegate_modifier* ‘delegate‘ return_type 3 identifier variant_type_parameter_list? 4 ‘(‘ formal_parameter_list? ‘)‘ type_parameter_constraints_clause* ‘;‘ 5 ; 6 7 delegate_modifier 8 : ‘new‘ 9 | ‘public‘ 10 | ‘protected‘ 11 | ‘internal‘ 12 | ‘private‘ 13 | delegate_modifier_unsafe 14 ;
同一修飾符在委托聲明中多次出現是編譯時錯誤。
所述new
改性劑只允許在另一種類型的,在這種情況下,它指明這種委托隱藏同名一個繼承的成員,如在描述中聲明委托新的改性劑。
在public
,protected
,internal
,和private
委托的類型名稱是標識符。
可選的formal_parameter_list指定委托的參數,return_type指示委托的返回類型。
可選的variant_type_parameter_list(Variant類型參數列表)指定委托本身的類型參數。
委托類型的返回類型必須是void
或輸出安全(方差安全)。
委托類型的所有形式參數類型必須是輸入安全的。此外,任何out
或ref
參數類型也必須是輸出安全的。請註意out
,由於底層執行平臺的限制,甚至參數都需要輸入安全。
C#中的委托類型是名稱等價的,在結構上不等同。具體而言,具有相同參數列表和返回類型的兩種不同委托類型被視為不同的委托類型。但是,兩個不同但結構上等效的委托類型的實例可以比較為相等(委托相等運算符)。
1 delegate int D1(int i, double d); 2 3 class A 4 { 5 public static int M1(int a, double b) {...} 6 } 7 8 class B 9 { 10 delegate int D2(int c, double d); 11 public static int M1(int f, double g) {...} 12 public static void M2(int k, double l) {...} 13 public static int M3(int g) {...} 14 public static void M4(int g) {...} 15 }
這些方法A.M1
和B.M1
與雙方委托類型兼容D1
和D2
,因為它們具有相同的返回類型和參數列表; 但是,這些委托類型是兩種不同的類型,因此它們不可互換。的方法B.M2
,B.M3
以及B.M4
與委托類型不兼容D1
和D2
,因為他們有不同的返回類型或參數列表。
與其他泛型類型聲明一樣,必須提供類型參數以創建構造的委托類型。通過為委托聲明中的每個類型參數替換構造的委托類型的相應類型參數,來創建構造的委托類型的參數類型和返回類型。生成的返回類型和參數類型用於確定哪些方法與構造的委托類型兼容。例如:
1 delegate bool Predicate<T>(T value); 2 3 class X 4 { 5 static bool F(int i) {...} 6 static bool G(string s) {...} 7 }
該方法X.F
與委托類型兼容,Predicate<int>
並且該方法X.G
與委托類型兼容Predicate<string>
。
聲明委托類型的唯一方法是通過delegate_declaration。委托類型是派生自的類類型System.Delegate
。委托類型是隱式的sealed
,因此不允許從委托類型派生任何類型。也不允許從中派生非委托類類型System.Delegate
。請註意,System.Delegate
它本身不是委托類型; 它是一個類類型,從中派生所有委托類型。
C#為委托實例化和調用提供了特殊語法。除了實例化之外,任何可以應用於類或類實例的操作也可以分別應用於委托類或實例。特別是,可以System.Delegate
通過通常的成員訪問語法訪問該類型的成員。
由委托實例封裝的方法集稱為調用列表。當從單個方法創建委托實例(委托兼容性)時,它封裝該方法,並且其調用列表僅包含一個條目。但是,當組合兩個非null委托實例時,它們的調用列表將按照左操作數和右操作數的順序連接,以形成一個新的調用列表,其中包含兩個或多個條目。
使用二進制+
(加法運算符)和+=
運算符(復合賦值)組合委托。可以使用二進制-
(減法運算符)和-=
運算符(復合賦值)從委托組合中刪除委托。可以比較委托的相等性(委托相等運算符)。
以下示例顯示了許多委托的實例化及其相應的調用列表:
1 delegate void D(int x); 2 3 class C 4 { 5 public static void M1(int i) {...} 6 public static void M2(int i) {...} 7 8 } 9 10 class Test 11 { 12 static void Main() { 13 D cd1 = new D(C.M1); // M1 14 D cd2 = new D(C.M2); // M2 15 D cd3 = cd1 + cd2; // M1 + M2 16 D cd4 = cd3 + cd1; // M1 + M2 + M1 17 D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 18 } 19 20 }
當cd1
和cd2
實例化時,它們都封裝了一個方法。在cd3
實例化時,它具有兩個方法的調用列表,M1
並按M2
順序。cd4
的調用列表包含M1
,, M2
和M1
,按順序。最後cd5
的調用列表中包含M1
,M2
,M1
,M1
,並M2
按此順序。有關組合(以及刪除)委托的更多示例,請參閱委派調用。
委派兼容性
的方法或委托M
是兼容與委托類型D
如果以下所有條件都為真:
D
並且M
具有相同數量的參數,並且每個參數與相應的參數D
具有相同ref
或out
修飾符M
。- 對於每個值參數(帶有no
ref
或out
modifier 的參數),從參數類型到相應的參數類型中存在標識轉換(標識轉換)或隱式引用轉換(隱式引用轉換)。D
M
- 對於每個
ref
或out
參數,參數類型in與參數類型D
相同M
。 - 從返回類型
M
到返回類型的存在標識或隱式引用轉換D
。
委托實例化
委托的實例由delegate_creation_expression(委托創建表達式)或轉換為委托類型創建。新創建的委托實例然後引用:
- delegate_creation_expression中引用的靜態方法,或
null
在delegate_creation_expression中引用的目標對象(不可以)和實例方法,或- 另一位委托。
例如:
1 delegate void D(int x); 2 3 class C 4 { 5 public static void M1(int i) {...} 6 public void M2(int i) {...} 7 } 8 9 class Test 10 { 11 static void Main() { 12 D cd1 = new D(C.M1); // static method 13 C t = new C(); 14 D cd2 = new D(t.M2); // instance method 15 D cd3 = new D(cd2); // another delegate 16 } 17 }
實例化後,委托實例始終引用相同的目標對象和方法。請記住,當兩個委托組合在一起,或者一個委托從另一個委托中刪除時,新的委托會產生自己的調用列表; 組合或刪除的委托的調用列表保持不變。
委托調用
C#提供了調用委托的特殊語法。當調用其調用列表包含一個條目的非null委托實例時,它將調用具有相同參數的one方法,並返回與引用方法相同的值。(有關委托調用的詳細信息,請參閱委派調用。)如果在調用此類委托期間發生異常,並且該異常未在調用的方法中捕獲,則在調用的方法中繼續搜索異常catch子句委托,就好像該方法直接調用了該委托引用的方法一樣。
通過按順序同步調用調用列表中的每個方法來調用其調用列表包含多個條目的委托實例。所謂的每個方法都傳遞給委托實例的同一組參數。如果這樣的委托調用包含引用參數(引用參數),則每個方法調用都將引用同一個變量; 通過調用列表中的一個方法對該變量的更改將對調用列表中的下一個方法可見。如果委托調用包括輸出參數或返回值,則它們的最終值將來自列表中最後一個委托的調用。
如果在處理調用此類委托期間發生異常,並且該異常未在調用的方法中捕獲,則在調用委托的方法中繼續搜索異常catch子句,並在調用之後繼續執行任何方法列表未被調用。
嘗試調用值為null的委托實例會導致類型異常System.NullReferenceException
。
以下示例顯示如何實例化,組合,刪除和調用委托:
1 using System; 2 3 delegate void D(int x); 4 5 class C 6 { 7 public static void M1(int i) { 8 Console.WriteLine("C.M1: " + i); 9 } 10 11 public static void M2(int i) { 12 Console.WriteLine("C.M2: " + i); 13 } 14 15 public void M3(int i) { 16 Console.WriteLine("C.M3: " + i); 17 } 18 } 19 20 class Test 21 { 22 static void Main() { 23 D cd1 = new D(C.M1); 24 cd1(-1); // call M1 25 26 D cd2 = new D(C.M2); 27 cd2(-2); // call M2 28 29 D cd3 = cd1 + cd2; 30 cd3(10); // call M1 then M2 31 32 cd3 += cd1; 33 cd3(20); // call M1, M2, then M1 34 35 C c = new C(); 36 D cd4 = new D(c.M3); 37 cd3 += cd4; 38 cd3(30); // call M1, M2, M1, then M3 39 40 cd3 -= cd1; // remove last M1 41 cd3(40); // call M1, M2, then M3 42 43 cd3 -= cd4; 44 cd3(50); // call M1 then M2 45 46 cd3 -= cd2; 47 cd3(60); // call M1 48 49 cd3 -= cd2; // impossible removal is benign 50 cd3(60); // call M1 51 52 cd3 -= cd1; // invocation list is empty so cd3 is null 53 54 cd3(70); // System.NullReferenceException thrown 55 56 cd3 -= cd1; // impossible removal is benign 57 } 58 }
如語句所示cd3 += cd1;
,委托可以多次出現在調用列表中。在這種情況下,每次出現只需調用一次。在諸如此類的調用列表中,當刪除該委托時,調用列表中的最後一個實例是實際刪除的那個。
在執行最終語句之前,cd3 -= cd1;
委托cd3
引用空的調用列表。嘗試從空列表中刪除委托(或從非空列表中刪除不存在的委托)不是錯誤。
產生的輸出是:
1 C.M1: -1 2 C.M2: -2 3 C.M1: 10 4 C.M2: 10 5 C.M1: 20 6 C.M2: 20 7 C.M1: 20 8 C.M1: 30 9 C.M2: 30 10 C.M1: 30 11 C.M3: 30 12 C.M1: 40 13 C.M2: 40 14 C.M3: 40 15 C.M1: 50 16 C.M2: 50 17 C.M1: 60 18 C.M1: 60
C#6.0語言規範(十五) 委托