C# Delegates: Step by Step

C# Delegates – Step by Step

At first glance, delegates are difficult to absorb
because the usage of a delegate runs against conventional thinking on a
how a method should be called. During a conventional method call, you
create an object that contains the methods you want to call, invoke the
method, passing the required parameters. Everything happens in one pop,
including defining what object and method to call and invoking the call.
The method call through delegation takes a different approach. It
splits up the definition of the method call and the actual invocation of
the method into two parts. Delegates are the .NET version of addresses
to methods. They are type-safe classes that define the return types and
types of parameters. The delegate class not only not only contains a
reference to a method, but holds references to multiple methods. Even
though delegates have been around since .NET 2.0, they play an important
role in .NET 4 today. Lambda Expressions are directly related to
delegates. When the parameter is a delegate type, you can use a Lambda
expression to implement a method that’s referenced from the delegate.
Delegates exist for situations in which you pass methods around to other
methods. To see what this means, consider this code :

The information encapsulated inside a delegate about a
method can be categorized into two groups: method signature and method
target. Method signature includes information about the number and type
of the method parameters as well as the return type. Method target
includes the name of the method and the object in which the method
resides. When we create a delegate object that encapsulates a method
call, we must provide these two sets of information.

The first thing we need to do to use delegation in
our program is to define a delegate class by specifying the signature of
the method the delegate class is capable of handling. In our example,
we defined a delegate class called CalculationHandler that is capable of
handling a method with two integer parameters and an integer return
value. If the method doesn’t have any return value, then we must use
“void” instead. After we have defined CalculationHandler, it becomes the
inner class of the existing class (in our case, the Test class).

After we have defined a delegate class, the next step
is to create an instance of the class and bind it to the specific
target. It is important to note that, first, the delegate class has only
one constructor, which takes the method target as its only parameter.
This binds the delegate object to a physical target. Second, the method
target specified in the constructor must match the method signature
defined in the delegate class. That is, we have to make sure that the
Sum method matches with the definition of CalculationHandler, which says
that the target method must take two integer parameters and have an
integer return value.

In this next example we instantiate a delegate of
type GetAString, and then initialize it so it refers to the ToString()
method of the integer variable x. Delegates in C# always syntactically
take one-parameter constructor, the parameter being the method to which
the delegate will refer. This method must match the signature with which
you originally defined the delegate. So in this case, we would get a
compilation error if we tried to initialize the variable
firstStringMethod with any method that did not take any parameters and
return a string. Notice that, because int.ToString() is an instance
method (as opposed to a static one), we need to specify the instance ( x
) as well as the name of the method to initialize the delegate
properly. The next line actually uses the delegate to display the
string. In any code, supplying the name of a delegate instance, followed
by brackets containing any parameters, has exactly the same effect as
calling the method wrapped by the delegate.

Using operations[0]:
Value is2, result of operation is4
Value is7.94, result of operation is15.88
Value is1.414, result of operation is2.828
Using operations[1]:
Value is2, result of operation is4
Value is7.94, result of operation is63.0436
Value is1.414, result of operation is1.999396

In this code, we instantiate an array of DoubleOp
delegates (remember that after we have defined a delegate class, you can
basically instantiate instances just as we can with normal classes, so
putting some into an array is no problem). Each element of the array
gets initialized to refer to a different operation implemented by the
MathOperations class. Then, we loop through the array, applying each
operation to three different values. This illustrates one way of using
delegates — that you can group methods together into an array using
them, so that you can call several methods in a loop. The key lines in
this code are the ones in which you actually pass each delegate to the
ProcessAndDisplayNumber() method, for example:

# Sample Snippet #

ProcessAndDisplayNumber(operations[i], 2.0);

Here is an example that should clarify the meaning of
delegates. Notice how there are two categories of information for each
instance. As we have seen, this indirection between two objects
promotes “decoupling”. Putting a delegate, or method object between a
client and a server enables us to define certain actions without
invoking them. This could be advantageous when running multiple
applications to achieve a task. We would want each application to have
little knowledge of the other; therefore, if one application changes in
behavior, say the one providing a service, then the application
consuming the service will not alter its behavior. The consumer of the
service will not change its operating procedures if the service provider
suddenly changes its operating procedures. The example below, however,
is meant to stick with the general principals of delegates:

Review

To put delegates to use, you need to define one or
more handler methods, assign the handler when instantiating a delegate,
and perform an invocation on the delegate instance. Here is an
appropriate example:

When we use a delegate to encapsulate method calls,
our mindset must shift from the conventional view of method calls to an
object-oriented view. Asynchronous programming produces powerful
applications for delegates. Similar to waiting on a long, drawn out
shopping line and being, idle, perhpas an analogy would be performing a
task while waiting for the person in front of you.