21.1 一般規定
委派宣告會定義衍生自 類別的類別 System.Delegate。 委派實例會封裝調用清單,這是一或多個方法的清單,每個方法稱為可呼叫的實體。 對於實例方法,可呼叫的實體是由實例和該實例上的方法所組成。 對於靜態方法,可呼叫的實體只包含方法。 叫用具有一組適當自變數的委派實例,會導致每個委派的可呼叫實體使用指定的自變數集合來叫用。
注意:委派實例的一個有趣且有用的屬性是它不知道或關心它封裝的方法的類別;重要的是這些方法與委派的類型相容 (§21.4)。 這讓委派完全適合「匿名」調用。 結尾註釋
21.2 委託聲明
delegate_declaration是宣告新委派類型的type_declaration (~14.7)。
delegate_declaration
: attributes? delegate_modifier* 'delegate' return_type delegate_header
| attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
delegate_header
;
delegate_header
: identifier '(' parameter_list? ')' ';'
| identifier variant_type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier 定義見 §24.2。
同一個修飾詞在委派宣告中出現多次是編譯時期錯誤。
提供variant_type_parameter_list的委派宣告是泛型委派宣告。 此外,泛型類別宣告或泛型結構宣告內的任何委派都是泛型委派宣告,因為應該提供包含型別的型別自變數來建立建構型別 ({8.4)。
new只有在另一種類型內宣告的委派上才允許修飾詞,在此情況下,它會指定這類委派會以相同名稱隱藏繼承的成員,如 \15.3.5 中所述。
public、 protectedinternal和 private 修飾詞可控制委派類型的存取範圍。 根據委派宣告發生的內容而定,可能不允許其中一些修飾詞 ({7.5.2)。
委派的類型名稱是 標識碼。
如同方法 (~15.6.1),如果 ref 存在,則委派會傳回 by-ref;否則,如果 return_type 為 void,則委派會傳回 no-no-value;否則,委派會傳回逐一值。
選擇性 parameter_list 會指定委派的參數。
傳 回值或 returns-no-value 委派宣告的return_type 會指定委派傳回的結果類型,如果有的話。
returns-by-ref 委派宣告的ref_return_type會指定委派所傳回之variable_reference ({9.5) 所參考的變數類型。
選擇性 variant_type_parameter_list (§19.2.3) 會指定委派本身的類型參數。
委派類型的傳回類型應該是 void或輸出安全 (§19.2.3.2) 。
委派類型的所有參數類型都應該是輸入安全的 (§19.2.3.2) 。 此外,任何輸出或參考參數類型也應該是輸出安全。
注意:由於常見的實作限制,輸出參數必須安全輸入。 結尾註釋
此外,委派的任何類型參數上,每個類別類型條件約束、介面類型條件約束和類型參數條件約束都應該是輸入安全。
C# 中的委派類型是名稱相等的,在結構上不相等。
範例:
delegate int D1(int i, double d); delegate int D2(int c, double d);委派型
D1別和D2是兩個不同的類型,因此它們不可以互換,儘管其簽章相同。end 範例
如同其他泛型型別宣告,應該提供型別自變數來建立建構的委派型別。 建構委派類型的參數類型和傳回型別是由替代委派宣告中每個類型參數所建立,這是建構委派型別的對應型別自變數。
宣告委派型別的唯一 方式是透過delegate_declaration。 每個委派類型都是衍生自 System.Delegate的參考型別。 每個委派類型所需的成員詳見 §21.3。 委派類型是隱含的 sealed,因此不允許從委派類型衍生任何類型。 也不允許宣告衍生自 System.Delegate的非委派類別類型。
System.Delegate 本身不是委派類型;它是衍生所有委派型別的類別類型。
21.3 代表成員
每個委派類型都會從 類別繼承成員,Delegate如 \15.3.4 中所述。 此外,每個委派類型都應該提供非泛型Invoke方法,其參數清單符合委派宣告中的parameter_list,其傳回型別符合委派宣告中的return_type或ref_return_type,以及傳回ref_kind符合委派宣告中之ref_kind的傳回委派。 方法 Invoke 至少可以和包含委派型別一樣可存取。 在委派類型上呼叫方法在 Invoke 語意上等同於使用委派調用語法 (§21.6) 。
實作可能會在委派類型中定義其他成員。
除了具現化之外,可以套用至類別或類別實例的任何作業也可以分別套用至委派類別或實例。 特別是,可以透過一般成員存取語法來存取類型的成員 System.Delegate 。
21.4 委派相容性
如果下列所有專案都成立,方法或委派類型M
-
D和M具有相同數目的參數,且 中的每個D參數與 中的M對應參數具有相同的參考參數修飾詞。 - 針對每個值參數,識別轉換 (~10.2.2) 或隱含參考轉換 (~10.2.8) 會從 中的
D參數類型到 中M對應的參數類型。 - 針對每個參考參數,中的
D參數類型與 中的M參數類型相同。 - 下列其中一項成立:
此相容性定義允許傳回型別中的共變數和參數類型的反變數。
範例:
delegate int D1(int i, double d); delegate int D2(int c, double d); delegate object D3(string s); class A { public static int M1(int a, double b) {...} } class B { public static int M1(int f, double g) {...} public static void M2(int k, double l) {...} public static int M3(int g) {...} public static void M4(int g) {...} public static object M5(string s) {...} public static int[] M6(object o) {...} }和方法
A.M1B.M1都與委派類型和D1D2相容,因為它們具有相同的傳回類型和參數清單。 方法B.M2、B.M3和B.M4與 委派類型不相容,因為它們有不同的傳回型D1D2別或參數清單。 和方法B.M5B.M6都與委派類型D3相容。end 範例
範例:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }方法
X.F與委派類型Predicate<int>相容,而且 方法X.G與委派類型Predicate<string>相容。end 範例
注意:委派相容性的直覺意義在於,如果委派的每個調用都可以取代為方法的調用,而不違反類型安全性,將選擇性參數和參數數位視為明確參數,則方法與委派類型相容。 例如,在下列程式代碼中:
delegate void Action<T>(T arg); class Test { static void Print(object value) => Console.WriteLine(value); static void Main() { Action<string> log = Print; log("text"); } }方法
Action<string>委派類型相容,因為委派的任何Action<string>調用也會是方法的有效調用如果上述方法的
Print(object value, bool prependTimestamp = false),例如,則依據本子句的規則,該Action<string>相容。結尾註釋
21.5 委派實例化
委派的實例可以透過 delegate_creation_expression(§12.8.17.5)來建立,也可以轉換為委派類型、進行委派組合或委派移除。 新建立的委派實例接著會參考一或多個:
- delegate_creation_expression 中所參考的靜態方法,或
- 目標物件(不能為
null)和delegate_creation_expression中參考的實例方法,或 - 另一個委派 (§12.8.17.5)。
範例:
delegate void D(int x); class C { public static void M1(int i) {...} public void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // Static method C t = new C(); D cd2 = new D(t.M2); // Instance method D cd3 = new D(cd2); // Another delegate } }end 範例
委派實例所封裝的方法集合稱為 調用清單。 從單一方法建立委派實例時,它會封裝該方法,而且其調用清單只包含一個專案。 不過,當兩個非null 委派實例結合時,其調用清單會串連在左操作數,然後依右操作數的順序組成新的調用清單,其中包含兩個或多個專案。
從單一委託建立新的委託時,產生的調用列表只有一個條目,即來源委託 (§12.8.17.5)。
委派會使用二進位檔 + (§12.12.5) 和 += 運算子 (§12.23.4) 來組合。 您可以使用二進位檔 - (§12.12.6) 和 -= 運算子 (§12.23.4) 從委派組合中移除委派。 可以比較代表的相等性 (§12.14.9)。
範例:下列範例顯示一些委派的具現化,以及其對應的調用清單:
delegate void D(int x); class C { public static void M1(int i) {...} public static void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // M1 - one entry in invocation list D cd2 = new D(C.M2); // M2 - one entry D cd3 = cd1 + cd2; // M1 + M2 - two entries D cd4 = cd3 + cd1; // M1 + M2 + M1 - three entries D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 - five entries D td3 = new D(cd3); // [M1 + M2] - ONE entry in invocation // list, which is itself a list of two methods. D td4 = td3 + cd1; // [M1 + M2] + M1 - two entries D cd6 = cd4 - cd2; // M1 + M1 - two entries in invocation list D td6 = td4 - cd2; // [M1 + M2] + M1 - two entries in invocation list, // but still three methods called, M2 not removed. } }當 和
cd1具現化時cd2,它們都會封裝一個方法。 具現化時cd3,它會有兩個方法的調用清單,M1並以M2該順序排列。cd4的呼叫清單會依該順序包含M1、M2和M1。 針對cd5,呼叫清單會依該順序包含M1、M2、M1、M1和M2。從具有delegate_creation_expression的另一個委派建立委派 時, 結果具有與原始結構不同的調用清單,但會導致使用相同的順序叫用相同的方法。 從
td3其調用清單中建立時cd3,只有一個成員,但該成員是方法M1M2的清單,而且這些方法td3的叫用順序與 叫cd3用的順序相同。 同樣地,當td4具現化其調用清單只有兩個專案,但是它會依該順序M1叫用三個方法M2、M1和cd4。調用清單的結構會影響委派減法。 委派
cd6,其建立方式是從cd2減去 (叫M2cd4M1用、M2和M1) 叫用M1和 。M1不過,藉由從td6減去 (cd2M2叫td4用、 和M1)M2所建立的委派M1,仍會依該順序叫M1用 、M2和M1,如同M2清單中不是單一專案,而是巢狀清單的成員。 如需合併 (以及移除) 委派的更多範例,請參閱 §21.6。end 範例
具現化后,委派實例一律會參考相同的調用清單。
注意:請記住,當兩個委派合併或一個從另一個委派移除時,具有其本身調用清單的新委派結果;合併或移除的委派調用清單會保持不變。 結尾註釋
21.6 委派調用
C# 提供叫用委派的特殊語法。 當呼叫其調用清單包含一個項目的非null 委派實例時,它會呼叫該方法,使用相同參數,並返回與被引用方法相同的值。 (如需委派調用的詳細資訊,請參閱 §12.8.10.4。)如果在調用此類委派期間發生例外狀況,而且該例外狀況未在所調用的方法內被攔截,則將在呼叫此委派的方法中繼續搜尋例外狀況的 catch 子句,就如同該方法直接調用了委派所指向的方法一樣。
包含多個項目的調用清單的委派實例會依次同步地調用調用清單中的每一個方法。 所呼叫的每個方法都會傳遞與委派實例相同的自變數集。 如果這類委派調用包含參考參數 (~15.6.2.3.3.3),則每個方法調用都會與相同變數的參考一起發生;叫用清單中的一個方法對該變數所做的變更將會在調用清單的更下一步的方法中看見。 如果委派調用包含輸出參數或傳回值,其最終值會來自清單中最後一個委派的調用。 如果在處理這類委派的叫用期間發生例外狀況,而且叫用的方法內不會攔截該例外狀況,則搜尋例外狀況 catch 子句會繼續於呼叫委派的方法中,而且不會叫用清單之後的任何方法。
嘗試叫用值會導致 null 類型 System.NullReferenceException例外狀況的委派實例。
範例:下列範例示範如何具現化、合併、移除和叫用委派:
delegate void D(int x); class C { public static void M1(int i) => Console.WriteLine("C.M1: " + i); public static void M2(int i) => Console.WriteLine("C.M2: " + i); public void M3(int i) => Console.WriteLine("C.M3: " + i); } class Test { static void Main() { D cd1 = new D(C.M1); cd1(-1); // call M1 D cd2 = new D(C.M2); cd2(-2); // call M2 D cd3 = cd1 + cd2; cd3(10); // call M1 then M2 cd3 += cd1; cd3(20); // call M1, M2, then M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); // call M1, M2, M1, then M3 cd3 -= cd1; // remove last M1 cd3(40); // call M1, M2, then M3 cd3 -= cd4; cd3(50); // call M1 then M2 cd3 -= cd2; cd3(60); // call M1 cd3 -= cd2; // impossible removal is benign cd3(60); // call M1 cd3 -= cd1; // invocation list is empty so cd3 is null // cd3(70); // System.NullReferenceException thrown cd3 -= cd1; // impossible removal is benign } }如 語句
cd3 += cd1;所示,委派可以多次出現在調用清單中。 在此情況下,只會在每個發生次數叫用一次。 在這樣的調用清單中,當移除該委派時,調用清單中的最後一個出現專案就是實際移除的叫用清單。在執行最後一個語句之前,
cd3 -= cd1委派cd3會緊接在參考空的調用清單。 嘗試從空白清單中移除委派(或從非空白清單中移除不存在的委派),不是錯誤。產生的輸出為:
C.M1: -1 C.M2: -2 C.M1: 10 C.M2: 10 C.M1: 20 C.M2: 20 C.M1: 20 C.M1: 30 C.M2: 30 C.M1: 30 C.M3: 30 C.M1: 40 C.M2: 40 C.M3: 40 C.M1: 50 C.M2: 50 C.M1: 60 C.M1: 60end 範例