Tuesday, 21 July 2015

Delegates


Delegates are similar to function pointers i.e. a delegate instance represents an object that is able to call a method. Thus, a delegate redirects execution to the function its been assigned to. This indirection is useful to decouple the caller from the actual method. Below is a quick example on how to use delegates in the simplest form.
public class DelegateSample
    {
        /* Declare delegate with return type and parameter */
        public delegate double Convert(double x);
 
        /* Main function */
        public static void Main(string[] args)
        {
            /* Assign function below to delegate */
            Convert c = InchToCm;
 
            /* Convert 5 inch to centimeters */
            double result = c(5);
 
            /* Print the result */
            Console.WriteLine(result);            
        }
 
        /* Convert inch to centimeters */
        private static double InchToCm(double x)
        {
            return x * 2.54;
        }
    }

The private method InchToCm() is assigned to the Convert delegate, which creates a new delegate instance. Therefore, Convert c = InchToCm is the short version for Convert c = new Convert(InchToCm). Note that the return type and the parameter type match the delegate declaration. Now we can indirectly call the InchToCm() method by using the delegate object c and pass in the parameter value of 5.

We can further expand this example by writing a plug-in method where we have an array of double values that represent measurements in inch and the goal is to convert all those values into centimeters using a delegate instance. 
/* Declare delegate with return type and parameter */
    public delegate double Convert(double x);
 
 
    /* Helper class */
    public class Converter
    {
        public static void Convert(double[] values, Convert c)
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = c(values[i]);
            }
        }
    }
 
    public class DelegateSample
    {
        /* Main function */
        public static void Main(string[] args)
        {
            double[] values = { 12.58.7553.25 };
 
            Converter.Convert(values, InchToCentimeters);
        }
 
        /* Convert inch to centimeters */
        private static double InchToCentimeters(double x)
        {
            return x * 2.54;
        }
    }

The example above uses the delegate declared outside of the class which is publicly available. The Converter helper class uses the delegate declaration for the parameter of the Convert method. This method takes the elements of the array and calls the method assigned to the delegate instance c. Furthermore, the DelegateSample class uses the static Convert function by passing in the array and the private method InchToCm whose method signature matches the delegate declaration. Therefore, we indirectly call the InchToCm method using the Converter helper class.


Multicast Delegates

Another capability of delegates is multi casting. A multicast delegate contains references to more than one method. The methods added to the delegate object are usually of type void. The execution flow depends on the sequence the methods have been added to the delegate object.
public class DelegateSample
    {
        /* Declare delegate with parameter */
        public delegate void Convert(double x);
 
        /* Main function */
        public static void Main(string[] args)
        {
            /* Add first method to delegate */
            Convert c = InchToCmToConsole;
 
            /* Add anoither method to the delegate object */
            c += InchToCmToFile;            
        }
 
        /* Convert inch to centimeters and print to Console */
        private static void InchToCmToConsole(double x)
        {
            Console.WriteLine(x * 2.54);
        }
 
        /* Convert inch to centimeters and write to file */
        private static void InchToCmToFile(double x) 
        { 
            System.IO.File.WriteAllText("result.txt", (x * 2.54).ToString()); 
        }       
    }

The line c += InchToCmToFile adds the method to the delegate object. Thus, the delegate object calls the methods InchToCmToConsole and InchToCmToFile which is the same sequence those methods have been added. The operators += and -= are used to add or remove a method from the delegate object respectively. Note that delegate objects are immutable; therefore, we add or remove delegate instances from the multicast delegate. If a multicast delegate contains a nonvoid return type, the caller will receive the return value from the last method that was called from the sequence.

Instance methods can also be assigned to delegate objects. In this case the delegate object must not only keep track of the reference to the method itself, but also to the instance that contains the method definition. The System.Delegate class contains the Target property that represents that instance. The target property will be null if the delegate object has a reference to a static method.


Generic Delegates

Delegates can also be used with generic methods. Below is another example on how to use generic delegates.
/* Declare delegate with generic return type and parameter */
        public delegate T Convert<T>(T param);
 
 
        /* Helper class */
        public class Converter
        {
            public static void Convert (T[] values, Convert<T> c)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    values[i] = c(values[i]);
                }
            }
        }
 
        public class DelegateSample
        {
            /* Main function */
            public static void Main(string[] args)
            {
                double[] values = { 12.58.7553.25 };
 
                Converter.Convert(values, InchToCentimeters);
            }
 
            /* Convert inch to centimeters */
            private static double InchToCentimeters(double x)
            {
                return x * 2.54;
            }
        }

Above we use T as type parameter for the delegate object as well as the Convert methods. 


Func and Action Delegates

There are two delegates available in the System namespace Func and Action. The Func delegate encapsulates a function with a return type of T and up to 16 parameters. For instance, to define a Func delegate with two parameters we write:
public delegate T Func<in T1, in T2, out T>(T1 param1, T2 param2);

The Action delegate does not return a value and can also instantiated with a maximum of 16 parameters. The below example uses two parameters:
public delegate void Action<in T1, in T2>(T1 param1, T2 param2);

Common use of Delegates

The most common use of delegates are events; however, delegates provide type safe callback definitions. Callbacks provide asynchronous feedback from the server to the client and are used for handling cross-thread marshalling in Windows Forms and WPF. Another use of delegates are in lambda expressions. The compiler converts lambda expressions into methods and creates a delegate that refers to that method.

References

[1] Albahari J., Albahari B. (2012). C# 5.0 In A Nutshell. Sebastopol, CA: O'Reilly Media

[2] Wagner B. (2010). Effective C# 2nd Edition. Boston, MA: Pearson Education Inc.