258 lines
14 KiB
Markdown
258 lines
14 KiB
Markdown
# Delegates
|
|
|
|
Delegates enable scenarios that other languages—such as C++, Pascal, and Modula -- have addressed with function pointers. 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.
|
|
|
|
A 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](delegates.md#delegate-declarations)) with the delegate's type. This makes delegates perfectly suited for "anonymous" invocation.
|
|
|
|
## Delegate declarations
|
|
|
|
A *delegate_declaration* is a *type_declaration* ([Type declarations](namespaces.md#type-declarations)) that declares a new delegate type.
|
|
|
|
```antlr
|
|
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.
|
|
|
|
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](classes.md#the-new-modifier).
|
|
|
|
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](basic-concepts.md#declared-accessibility)).
|
|
|
|
The delegate's type name is *identifier*.
|
|
|
|
The optional *formal_parameter_list* specifies the parameters of the delegate, and *return_type* indicates the return type of the delegate.
|
|
|
|
The optional *variant_type_parameter_list* ([Variant type parameter lists](interfaces.md#variant-type-parameter-lists)) specifies the type parameters to the delegate itself.
|
|
|
|
The return type of a delegate type must be either `void`, or output-safe ([Variance safety](interfaces.md#variance-safety)).
|
|
|
|
All the formal parameter types of a delegate type must be input-safe. Additionally, any `out` or `ref` parameter types must also be output-safe. Note that even `out` parameters are required to be input-safe, due to a limitation of the underlying execution platform.
|
|
|
|
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](expressions.md#delegate-equality-operators)).
|
|
|
|
For example:
|
|
|
|
```csharp
|
|
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) {...}
|
|
}
|
|
```
|
|
|
|
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. 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:
|
|
|
|
```csharp
|
|
delegate bool Predicate<T>(T value);
|
|
|
|
class X
|
|
{
|
|
static bool F(int i) {...}
|
|
static bool G(string s) {...}
|
|
}
|
|
```
|
|
|
|
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>` .
|
|
|
|
The only way to declare a delegate type is via a *delegate_declaration*. A delegate type is a class type that is derived from `System.Delegate`. Delegate types are implicitly `sealed`, so it is not permissible to derive any type from a delegate type. It is also not permissible to derive a non-delegate class type from `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# 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. 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](delegates.md#delegate-compatibility)) from a single method, it encapsulates that method, and its invocation list contains only one entry. 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.
|
|
|
|
Delegates are combined using the binary `+` ([Addition operator](expressions.md#addition-operator)) and `+=` operators ([Compound assignment](expressions.md#compound-assignment)). A delegate can be removed from a combination of delegates, using the binary `-` ([Subtraction operator](expressions.md#subtraction-operator)) and `-=` operators ([Compound assignment](expressions.md#compound-assignment)). Delegates can be compared for equality ([Delegate equality operators](expressions.md#delegate-equality-operators)).
|
|
|
|
The following example shows the instantiation of a number of delegates, and their corresponding invocation lists:
|
|
|
|
```csharp
|
|
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
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
When `cd1` and `cd2` are instantiated, they each encapsulate one method. When `cd3` is instantiated, it has an invocation list of two methods, `M1` and `M2`, in that order. `cd4`'s invocation list contains `M1`, `M2`, and `M1`, in that order. 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](delegates.md#delegate-invocation).
|
|
|
|
## Delegate compatibility
|
|
|
|
A method or delegate `M` is ***compatible*** with a delegate type `D` if all of the following are true:
|
|
|
|
* `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`.
|
|
* For each value parameter (a parameter with no `ref` or `out` modifier), an identity conversion ([Identity conversion](conversions.md#identity-conversion)) or implicit reference conversion ([Implicit reference conversions](conversions.md#implicit-reference-conversions)) exists from the parameter type in `D` to the corresponding parameter type in `M`.
|
|
* For each `ref` or `out` parameter, the parameter type in `D` is the same as the parameter type in `M`.
|
|
* An identity or implicit reference conversion exists from the return type of `M` to the return type of `D`.
|
|
|
|
## Delegate instantiation
|
|
|
|
An instance of a delegate is created by a *delegate_creation_expression* ([Delegate creation expressions](expressions.md#delegate-creation-expressions)) or a conversion to a delegate type. The newly created delegate instance then refers to either:
|
|
|
|
* The static method referenced in the *delegate_creation_expression*, or
|
|
* The target object (which cannot be `null`) and instance method referenced in the *delegate_creation_expression*, or
|
|
* Another delegate.
|
|
|
|
For example:
|
|
|
|
```csharp
|
|
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# provides special syntax for invoking a delegate. 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. (See [Delegate invocations](expressions.md#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](classes.md#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.
|
|
|
|
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.
|
|
|
|
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:
|
|
|
|
```csharp
|
|
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
|
|
}
|
|
}
|
|
```
|
|
|
|
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.
|
|
|
|
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
|
|
```
|