Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Delegates: Step by Step

0.00/5 (No votes)
11 Sep 2010 1  
An article to help the beginner understand delegates

Introduction

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

using System;
public class Test
 {
  public delegate int CalculationHandler(int x, int y);
  static void Main(string[]  args)
   {
     Math math = new Math();
    //create a new instance of the delegate class

     CalculationHandler sumHandler = new CalculationHandler(math.Sum);
    //invoke the delegate
      int result = sumHandler(8,9);
     Console.WriteLine("Result is: " + result);
     }
   }

 public class Math
  {
    public int Sum(int x, int y)
     {
       return x + y;
     }
  }

Result is: 17

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 returns 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 System;
public class Program
 {
   public delegate string GetAString();
   public static void Main()
        {
            int x = 40;
            GetAString  firstStringMethod = x.ToString;
            Console.WriteLine("String is {0}", firstStringMethod());
            Currency balance = new Currency(34, 50);
             // firstStringMethod references an instance method
            firstStringMethod = balance.ToString;
            Console.WriteLine("String is {0}", firstStringMethod());
             // firstStringMethod references a static method
            firstStringMethod = new GetAString(Currency.GetCurrencyUnit);
            Console.WriteLine("String is {0}", firstStringMethod());

        }
    }

You see that there is a Currency class that creates an object:

using System;
public class Currency {
       public uint Dollars;
        public ushort Cents;
     public Currency(uint dollars, ushort cents)
        {
            this.Dollars = dollars;
            this.Cents = cents;
        }
    public override string ToString()
        {
            return string.Format("${0}.{1,-2:00}", Dollars, Cents);
        }
     public static string GetCurrencyUnit()
        {
            return "Dollar";
        }
    public static explicit operator Currency(float value)
        {
            checked
            {
                uint dollars = (uint)value;
                ushort cents = (ushort)((value - dollars) * 100);
                return new Currency(dollars, cents);
            }
        }
       public static implicit operator float(Currency value)
        {
            return value.Dollars + (value.Cents / 100.0f);
        }

        public static implicit operator Currency(uint value)
        {
            return new Currency(value, 0);
        }

        public static implicit operator uint(Currency value)
        {
            return value.Dollars;
        }
    }

csc.exe /t:library Currency.cs

csc.exe /r:Currency.dll Program.cs

String is 40
String is $34.50
String is Dollar

This example defines a MathOperations class that has a couple of static methods to perform two operations on doubles. Then we use delegates to call up these methods. The math class looks like this:

using System;
public class MathOperations
    {
        public static double MultiplyByTwo(double value)
        {
            return value * 2;
        }

        public static double Square(double value)
        {
            return value * value;
        }
    }

We call up these methods like this:

using System;
 delegate double DoubleOp(double x);

    class Application
    {
        static void Main()
        {
            DoubleOp[] operations =
            {
               MathOperations.MultiplyByTwo,
               MathOperations.Square
            };

            for (int i = 0; i < operations.Length; i++)
            {
                Console.WriteLine("Using operations[{0}]:", i);
                ProcessAndDisplayNumber(operations[i], 2.0);
                ProcessAndDisplayNumber(operations[i], 7.94);
                ProcessAndDisplayNumber(operations[i], 1.414);
                Console.WriteLine();
            }
        }

        static void ProcessAndDisplayNumber(DoubleOp action, double value)
        {
            double result = action(value);
            Console.WriteLine(
               "Value is {0}, result of operation is {1}", value, result);
        }
    }

Output:

Using operations[0]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 15.88
Value is 1.414, result of operation is 2.828

Using operations[1]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 63.0436
Value is 1.414, result of operation is 1.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:

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:

using System;
class Program
{
    /// Delegate declaration.
    delegate string UppercaseDelegate(string input);
      /// Delegate implementation 1.
    static string UppercaseFirst(string input)
    {
        char[] buffer = input.ToCharArray();
        buffer[0] = char.ToUpper(buffer[0]);
        return new string(buffer);
    }

    /// <summary>
    /// Delegate implementation 2.
    /// </summary>
    static string UppercaseLast(string input)
    {
        char[] buffer = input.ToCharArray();
        buffer[buffer.Length - 1] = char.ToUpper(buffer[buffer.Length - 1]);
        return new string(buffer);
    }
  
    /// Delegate implementation 3.
      static string UppercaseAll(string input)
    {
        return input.ToUpper();
    }

    /// <summary>
    /// Receives delegate type.
    /// </summary>
    static void WriteOutput(string input, UppercaseDelegate del)
    {
        Console.WriteLine("Your string before: {0}", input);
        Console.WriteLine("Your string after: {0}", del(input));
    }

    static void Main()
    {
        // Wrap the methods inside delegate instances and pass to the method.
        WriteOutput("gems", new UppercaseDelegate(UppercaseFirst));
        WriteOutput("gems", new UppercaseDelegate(UppercaseLast));
        WriteOutput("gems", new UppercaseDelegate(UppercaseAll));
    }
}

Yields:

Your string before: gems
Your string after: Gems
Your string before: gems
Your string after: gemS
Your string before: gems
Your string after: GEMS

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:

using System;

// 1. Define delegate.
public delegate double UnitConversion(double from);

class QuickDelegateDemo
{
    // 2. Define handler method.
    public static double FeetToInches(double feet)
    {
        return feet * 12;
    }

    static void Main()
    {
        // 3. Create delegate instance.
        UnitConversion doConversion = new UnitConversion(FeetToInches);

        Console.WriteLine("Feet to Inches Converter");
        Console.WriteLine("------------------------\n");
        
        Console.Write("Please enter feet:  ");
        double feet = Double.Parse(Console.ReadLine());

        // 4. Use delegate just like a method.
        double inches = doConversion(feet);

        Console.WriteLine("\n{0} Feet = {1} Inches.\n", feet, inches);
        Console.ReadLine();
    }
}

OUTPUT:

Feet to Inches Converter
------------------------

Please enter feet:  12

12 Feet = 144 Inches.

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, perhaps an analogy would be performing a task while waiting for the person in front of you.

History

  • 11th September, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here