Академический Документы
Профессиональный Документы
Культура Документы
15. Delegates
Delegates enable scenarios that other languagessuch as C++, Pascal, and Modulahave 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 delegates 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 (15.1) with the delegates type.
This makes delegates perfectly suited for anonymous invocation.
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 10.3.4.
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 (3.5.1).
The delegates 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 (13.1.3) specifies the type parameters to the delegate itself.
The return type of a delegate type must be either void, or output-safe (13.1.3.1).
407
C# LANGUAGE SPECIFICATION
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 limitiation
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 (7.9.8).
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) {...}
}
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:
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.
408
Chapter 15 Delegates
The set of methods encapsulated by a delegate instance is called an invocation list. When a delegate instance is
created (15.2) 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 concatenatedin the
order left operand then right operandto form a new invocation list, which contains two or more entries.
Delegates are combined using the binary + (7.8.4) and += operators (7.17.2). A delegate can be removed from
a combination of delegates, using the binary - (7.8.5) and -= operators (7.17.2). Delegates can be compared
for equality (7.10.8).
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);
D cd2 = new D(C.M2);
D cd3 = cd1 + cd2;
D cd4 = cd3 + cd1;
D cd5 = cd4 + cd3;
}
//
//
//
//
//
M1
M2
M1 + M2
M1 + M2 + M1
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. cd4s invocation list contains M1, M2, and M1, in that
order. Finally, cd5s invocation list contains M1, M2, M1, M1, and M2, in that order. For more examples of
combining (as well as removing) delegates, see 15.4.
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 (6.1.1) or
implicit reference conversion (6.1.6) 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.
The target object (which cannot be null) and instance method referenced in the delegate-creationexpression, or
409
C# LANGUAGE SPECIFICATION
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);
C t = new C();
D cd2 = new D(t.M2);
D cd3 = new D(cd2);
}
}
// static method
// instance method
// 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.
410
Chapter 15 Delegates
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);
C c = new C();
D cd4 = new D(c.M3);
cd3 += cd4;
cd3(30);
// call M1, M2, M1, then M3
cd3 -= cd1;
cd3(40);
// remove last M1
// call M1, M2, then M3
cd3 -= cd4;
cd3(50);
// call M1 then M2
cd3 -= cd2;
cd3(60);
// call M1
cd3 -= cd2;
cd3(60);
cd3 -= cd1;
//
// System.NullReferenceException thrown
cd3(70);
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.
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:
411
C# LANGUAGE SPECIFICATION
C.M1:
C.M2:
C.M1:
C.M2:
C.M1:
C.M2:
C.M1:
C.M1:
C.M2:
C.M1:
C.M3:
C.M1:
C.M2:
C.M3:
C.M1:
C.M2:
C.M1:
C.M1:
412
-1
-2
10
10
20
20
20
30
30
30
30
40
40
40
50
50
60
60