In Section 14.3.3, we introduced the concept of a delegate—an object that holds a reference to a method.1 You can call a method through a variable of a delegate type—thus delegating to the referenced method the responsibilty of performing a task. Delegates also allow you to pass methods to and from other methods. We introduced delegates in the context of GUI event handlers, but they’re used in many areas of the .NET Framework. For example, in Chapter 9, we introduced LINQ query syntax. The compiler converts such LINQ queries into calls to extension methods—many of which have delegate parameters. Figure 21.6 declares and uses a delegate type. In Section 21.11, we’ll use delegates in the context of LINQ extension methods.
Line 9 defines a delegate type named NumberPredicate
. A variable of this type can store a reference to any method that takes one int
argument and returns a bool
. A delegate type is declared by preceding a method header with keyword delegate
(placed after any access specifiers, such as public
or private
) and following the method header with a semicolon. A delegate
type declaration includes the method header only—the header describes a set of methods with specific parameters and a specific return type.
Line 16 declares evenPredicate
as a NumberPredicate
variable and initializes it with a reference to the expression-bodied IsEven
method (line 62). Since method IsEven
’s signature matches the NumberPredicate
delegate’s signature, IsEven
can be referenced by a variable of type NumberPredicate
. Variable evenPredicate
can now be used as an alias for method IsEven
. A NumberPredicate
variable can hold a reference to any method that receives an int
and returns a bool
. Lines 19–20 use variable evenPredicate
to call method IsEven
, then display the result. The method referenced by the delegate is called using the delegate variable’s name in place of the method’s name, as in
evenPredicate(4)
The real power of delegates is in passing method references as arguments to methods, as we do in this example with method FilterArray
(lines 42–59). The method takes as arguments
an int
array and
a NumberPredicate
that references a method used to filter the array elements.
The method returns a List<int>
containing only the int
s that satisfy the condition specified by the NumberPredicate
. FilterArray
returns a List
, because we don’t know in advance how many elements will be included in the result.
The foreach
statement (lines 49–56) calls the method referenced by the NumberPredicate
delegate (line 52) once for each element of the array. If the method call returns true
, the element is included in result
. The NumberPredicate
is guaranteed to return either true
or false
, because any method referenced by a NumberPredicate
must return a bool
—as specified by the definition of the NumberPredicate
delegate type (line 9). Line 23 passes to FilterArray
the int
array (numbers
) and the NumberPredicate
that references the IsEven
method (evenPredicate
). FilterArray
then calls the NumberPredicate
delegate on each array element. Line 23 assigns the List
returned by FilterArray
to variable evenNumbers
and line 26 calls method DisplayList
(lines 71–82) to display the results.
Line 29 calls method FilterArray
to select the odd numbers in the array. In this case, we pass the method name IsOdd
(defined in line 65) as FilterArray
’s second argument, rather than creating a NumberPredicate
variable. Line 32 displays the results showing only the odd numbers. Line 35 calls method FilterArray
to select the numbers greater than 5 in the array, using method IsOver5
(defined in line 68) as FilterArray
’s second argument. Line 38 displays the elements that are greater than 5.