委派Delegates

委派可讓其他語言(例如C++,Pascal 和 Modula)的案例能夠使用函式指標來解決。Delegates enable scenarios that other languages—such as C++, Pascal, and Modula -- have addressed with function pointers. 不過C++ ,不同于函式指標,委派是完全面向物件, C++而且與成員函式的指標不同,委派會封裝物件實例和方法。Unlike C++ function pointers, however, delegates are fully object oriented, and unlike C++ pointers to member functions, delegates encapsulate both an object instance and a method.

委派宣告會定義衍生自類別的類別,System.DelegateA delegate declaration defines a class that is derived from the class System.Delegate. 委派實例會封裝一個調用清單,這是一或多個方法的清單,每個方法都稱為可呼叫的實體。A delegate instance encapsulates an invocation list, which is a list of one or more methods, each of which is referred to as a callable entity. 若是實例方法,可呼叫的實體是由實例和該實例上的方法所組成。For instance methods, a callable entity consists of an instance and a method on that instance. 若為靜態方法,可呼叫的實體只會包含方法。For static methods, a callable entity consists of just a method. 使用一組適當的引數來叫用委派實例,會使每一個委派的可呼叫實體以一組指定的引數叫用。Invoking a delegate instance with an appropriate set of arguments causes each of the delegate's callable entities to be invoked with the given set of arguments.

委派實例有一個有趣且實用的屬性,就是它不知道或在意它所封裝之方法的類別;重點在於這些方法與委派的型別相容(委派宣告)。An interesting and useful property of a delegate instance is that it does not know or care about the classes of the methods it encapsulates; all that matters is that those methods be compatible (Delegate declarations) with the delegate's type. 這使得委派非常適合「匿名」調用。This makes delegates perfectly suited for "anonymous" invocation.

委派宣告Delegate declarations

Delegate_declaration是宣告新委派類型的type_declaration類型宣告)。A delegate_declaration is a type_declaration (Type declarations) that declares a new delegate type.

delegate_declaration
    : attributes? delegate_modifier* 'delegate' return_type
      identifier variant_type_parameter_list?
      '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';'
    ;

delegate_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | delegate_modifier_unsafe
    ;

在委派宣告中多次出現相同的修飾詞時,就會發生編譯時期錯誤。It is a compile-time error for the same modifier to appear multiple times in a delegate declaration.

@No__t-0 修飾詞只允許用於在另一個類型中宣告的委派,在此情況下,它會指定這類委派會隱藏具有相同名稱的繼承成員,如新修飾詞中所述。The new modifier is only permitted on delegates declared within another type, in which case it specifies that such a delegate hides an inherited member by the same name, as described in The new modifier.

@No__t-0、`protected`、`internal` 和 @no__t 3 修飾詞會控制委派類型的存取範圍。The public, protected, internal, and private modifiers control the accessibility of the delegate type. 根據發生委派宣告的內容,可能不允許其中一些修飾詞(宣告的存取範圍)。Depending on the context in which the delegate declaration occurs, some of these modifiers may not be permitted (Declared accessibility).

委派的類型名稱為identifierThe delegate's type name is identifier.

選擇性的formal_parameter_list會指定委派的參數,而return_type則表示委派的傳回型別。The optional formal_parameter_list specifies the parameters of the delegate, and return_type indicates the return type of the delegate.

選擇性的variant_type_parameter_listvariant 型別參數清單)會指定委派本身的型別參數。The optional variant_type_parameter_list (Variant type parameter lists) specifies the type parameters to the delegate itself.

委派類型的傳回類型必須是 void 或輸出安全(變異數安全性)。The return type of a delegate type must be either void, or output-safe (Variance safety).

委派類型的所有正式參數類型都必須是輸入安全的。All the formal parameter types of a delegate type must be input-safe. 此外,任何 @no__t 0 或 @no__t 1 參數類型也必須為輸出安全。Additionally, any out or ref parameter types must also be output-safe. 請注意,即使 out 參數是輸入安全的,因為基礎執行平臺的限制。Note that even out parameters are required to be input-safe, due to a limitation of the underlying execution platform.

中C#的委派類型為對等的名稱,不是結構上的對等用法。Delegate types in C# are name equivalent, not structurally equivalent. 具體而言,有兩個不同的委派類型具有相同的參數清單和傳回類型,會被視為不同的委派類型。Specifically, two different delegate types that have the same parameter lists and return type are considered different delegate types. 不過,兩個不同但結構相等之委派類型的實例,可能會比較為相等(委派相等運算子)。However, instances of two distinct but structurally equivalent delegate types may compare as equal (Delegate equality operators).

例如:For example:

delegate int D1(int i, double d);

class A
{
    public static int M1(int a, double b) {...}
}

class B
{
    delegate int D2(int c, double d);
    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) {...}
}

@No__t-0 和 B.M1 的方法與委派類型 D1D2 相容,因為它們具有相同的傳回類型和參數清單;不過,這些委派類型是兩個不同的類型,因此無法互換。The methods A.M1 and B.M1 are compatible with both the delegate types D1 and D2 , since they have the same return type and parameter list; however, these delegate types are two different types, so they are not interchangeable. @No__t-0、`B.M3` 和 B.M4 的方法與委派類型(D1D2)不相容,因為它們有不同的傳回類型或參數清單。The methods B.M2, B.M3, and B.M4 are incompatible with the delegate types D1 and D2, since they have different return types or parameter lists.

如同其他泛型型別宣告,必須提供類型引數,才能建立結構化委派類型。Like other generic type declarations, type arguments must be given to create a constructed delegate type. 建立結構化委派類型的參數類型和傳回型別,是藉由以委派宣告中的每個型別參數來取代,這是結構化委派型別的對應型別引數。The parameter types and return type of a constructed delegate type are created by substituting, for each type parameter in the delegate declaration, the corresponding type argument of the constructed delegate type. 產生的傳回型別和參數類型是用來判斷哪些方法與結構化的委派型別相容。The resulting return type and parameter types are used in determining what methods are compatible with a constructed delegate type. 例如:For example:

delegate bool Predicate<T>(T value);

class X
{
    static bool F(int i) {...}
    static bool G(string s) {...}
}

方法 X.F 與委派類型 Predicate<int> 相容,而方法 X.GPredicate<string> 的委派類型相容。The method X.F is compatible with the delegate type Predicate<int> and the method X.G is compatible with the delegate type Predicate<string> .

宣告委派類型的唯一方式是透過delegate_declarationThe only way to declare a delegate type is via a delegate_declaration. 委派類型是衍生自 System.Delegate 的類別類型。A delegate type is a class type that is derived from System.Delegate. 委派類型會隱含 sealed,因此不允許從委派類型衍生任何類型。Delegate types are implicitly sealed, so it is not permissible to derive any type from a delegate type. 也不允許從 System.Delegate 衍生非委派類別類型。It is also not permissible to derive a non-delegate class type from System.Delegate. 請注意,System.Delegate 本身不是委派類型;它是衍生所有委派類型的類別類型。Note that System.Delegate is not itself a delegate type; it is a class type from which all delegate types are derived.

C#提供委派具現化和調用的特殊語法。C# provides special syntax for delegate instantiation and invocation. 除了具現化以外,可以套用至類別或類別實例的任何作業,也可以分別套用至委派類別或實例。Except for instantiation, any operation that can be applied to a class or class instance can also be applied to a delegate class or instance, respectively. 特別的是,您可以透過一般的成員存取語法來存取 @no__t 0 類型的成員。In particular, it is possible to access members of the System.Delegate type via the usual member access syntax.

委派實例所封裝的方法集合稱為「調用清單」。The set of methods encapsulated by a delegate instance is called an invocation list. 從單一方法建立委派實例(委派相容性)時,它會封裝該方法,而其調用清單只會包含一個專案。When a delegate instance is created (Delegate compatibility) from a single method, it encapsulates that method, and its invocation list contains only one entry. 不過,當結合兩個非 null 委派實例時,會串連其調用清單--在 order left 運算元 then 右運算元--中,以形成新的調用清單,其中包含兩個或多個專案。However, when two non-null delegate instances are combined, their invocation lists are concatenated -- in the order left operand then right operand -- to form a new invocation list, which contains two or more entries.

委派會使用 binary +加號)和 @no__t 2 運算子(複合指派)結合。Delegates are combined using the binary + (Addition operator) and += operators (Compound assignment). 您可以使用 binary -減法運算子)和 @no__t 2 運算子(複合指派),從委派的組合中移除委派。A delegate can be removed from a combination of delegates, using the binary - (Subtraction operator) and -= operators (Compound assignment). 委派可以比較是否相等(委派相等運算子)。Delegates can be compared for equality (Delegate equality operators).

下列範例顯示一些委派的具現化,以及其對應的調用清單:The following example shows the instantiation of a number of delegates, and their corresponding invocation lists:

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
        D cd2 = new D(C.M2);      // M2
        D cd3 = cd1 + cd2;        // M1 + M2
        D cd4 = cd3 + cd1;        // M1 + M2 + M1
        D cd5 = cd4 + cd3;        // M1 + M2 + M1 + M1 + M2
    }

}

cd1cd2 具現化時,它們會分別封裝一種方法。When cd1 and cd2 are instantiated, they each encapsulate one method. cd3 具現化時,它會有兩個方法的調用清單,M1M2 (依該順序)。When cd3 is instantiated, it has an invocation list of two methods, M1 and M2, in that order. cd4 的調用清單包含 M1M2M1 (依該順序)。cd4's invocation list contains M1, M2, and M1, in that order. 最後,cd5 的調用清單包含 M1M2M1M1M2 (依該順序)。Finally, cd5's invocation list contains M1, M2, M1, M1, and M2, in that order. 如需結合(以及移除)委派的更多範例,請參閱委派調用For more examples of combining (as well as removing) delegates, see Delegate invocation.

委派相容性Delegate compatibility

如果下列所有條件都成立,則方法或委派 M 與委派類型 D相容A method or delegate M is compatible with a delegate type D if all of the following are true:

  • DM 的參數數目相同,而且 D 中的每個參數與 M 中的對應參數具有相同的 refout 修飾詞。D and M have the same number of parameters, and each parameter in D has the same ref or out modifiers as the corresponding parameter in M.
  • 針對每個值參數(不含 @no__t 的參數或 out 修飾詞),識別轉換(識別轉換)或隱含參考轉換(隱含參考轉換)會從 D 中的參數類型到M 中的對應參數類型。For each value parameter (a parameter with no ref or out modifier), an identity conversion (Identity conversion) or implicit reference conversion (Implicit reference conversions) exists from the parameter type in D to the corresponding parameter type in M.
  • 針對每個 ref 或 @no__t 1 參數,D 中的參數類型與 M 中的參數類型相同。For each ref or out parameter, the parameter type in D is the same as the parameter type in M.
  • 識別或隱含參考轉換存在於從 M 傳回類型到 D 的傳回類型。An identity or implicit reference conversion exists from the return type of M to the return type of D.

委派具現化Delegate instantiation

委派的實例是由delegate_creation_expression委派建立運算式)或委派類型的轉換所建立。An instance of a delegate is created by a delegate_creation_expression (Delegate creation expressions) or a conversion to a delegate type. 新建立的委派實例則是指:The newly created delegate instance then refers to either:

  • Delegate_creation_expression中所參考的靜態方法,或The static method referenced in the delegate_creation_expression, or
  • Delegate_creation_expression中參考的目標物件(不可以是 null)和實例方法,或The target object (which cannot be null) and instance method referenced in the delegate_creation_expression, or
  • 另一個委派。Another delegate.

例如:For example:

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
    }
}

一旦具現化之後,委派實例一律會參考相同的目標物件和方法。Once instantiated, delegate instances always refer to the same target object and method. 請記住,當兩個委派合併,或其中一個被移除時,新的委派結果會有自己的調用清單;結合或移除委派的調用清單會保持不變。Remember, when two delegates are combined, or one is removed from another, a new delegate results with its own invocation list; the invocation lists of the delegates combined or removed remain unchanged.

委派調用Delegate invocation

C#提供用來叫用委派的特殊語法。C# provides special syntax for invoking a delegate. 當叫用清單中包含一個專案的非 null 委派實例被叫用時,它會使用指定的相同引數叫用一個方法,並傳回與所參考方法相同的值。When a non-null delegate instance whose invocation list contains one entry is invoked, it invokes the one method with the same arguments it was given, and returns the same value as the referred to method. (如需委派調用的詳細資訊,請參閱委派調用)。如果在這類委派的調用期間發生例外狀況,而且該例外狀況不會在叫用的方法內攔截,則搜尋例外狀況 catch 子句會繼續在呼叫委派的方法中,如同該方法直接呼叫委派所參考的方法。(See Delegate invocations for detailed information on delegate invocation.) If an exception occurs during the invocation of such a delegate, and that exception is not caught within the method that was invoked, the search for an exception catch clause continues in the method that called the delegate, as if that method had directly called the method to which that delegate referred.

叫用其調用清單包含多個專案的委派實例,會依照順序以同步方式叫用調用清單中的每個方法繼續進行。Invocation of a delegate instance whose invocation list contains multiple entries proceeds by invoking each of the methods in the invocation list, synchronously, in order. 呼叫的每個方法都會傳遞與指定給委派實例相同的引數集。Each method so called is passed the same set of arguments as was given to the delegate instance. 如果這類委派調用包含參考參數(參考參數),則會發生具有相同變數參考的每個方法調用;此變數在調用清單中的方法變更,將會在調用清單中進一步向下顯示。If such a delegate invocation includes reference parameters (Reference parameters), each method invocation will occur with a reference to the same variable; changes to that variable by one method in the invocation list will be visible to methods further down the invocation list. 如果委派調用包含輸出參數或傳回值,其最終值會來自清單中最後一個委派的調用。If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.

如果在處理這類委派的調用期間發生例外狀況,而且該例外狀況不會在叫用的方法內攔截,則搜尋例外狀況 catch 子句會繼續在呼叫委派的方法中,並于後續的任何方法中執行不會叫用調用清單。If an exception occurs during processing of the invocation of such a delegate, and that exception is not caught within the method that was invoked, the search for an exception catch clause continues in the method that called the delegate, and any methods further down the invocation list are not invoked.

嘗試叫用其值為 null 的委派實例,會導致類型 System.NullReferenceException 的例外狀況。Attempting to invoke a delegate instance whose value is null results in an exception of type System.NullReferenceException.

下列範例示範如何具現化、結合、移除和叫用委派:The following example shows how to instantiate, combine, remove, and invoke delegates:

using System;

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; 的語句所示,一個委派可以出現在調用清單中多次。As shown in the statement cd3 += cd1;, a delegate can be present in an invocation list multiple times. 在此情況下,它只會在每次發生時叫用一次。In this case, it is simply invoked once per occurrence. 在這類調用清單中,移除該委派時,在調用清單中最後一次出現的專案就是實際移除的。In an invocation list such as this, when that delegate is removed, the last occurrence in the invocation list is the one actually removed.

在最後一個語句執行之前,cd3 -= cd1;,委派 cd3 指的是空的調用清單。Immediately prior to the execution of the final statement, cd3 -= cd1;, the delegate cd3 refers to an empty invocation list. 嘗試從空白清單中移除委派(或從非空白清單中移除不存在的委派)並不是錯誤。Attempting to remove a delegate from an empty list (or to remove a non-existent delegate from a non-empty list) is not an error.

產生的輸出為:The output produced is:

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: 60