Wednesday, May 12, 2010

Generic Delegates

With the introduction of Generics in .Net, the task of declaring delegates got much simplified. Before this we had to declare a pretty big list of delegates for every callback methods and thereafter creating instances of each delegate. This seems a bit dragging due to the repetitive nature of declaring and instantiating delegates. Here let me show you the usual way of declaring delegates.

    public class DelegateDilemma
    {
        private delegate void Delg_NotifyUser(string Message);
        private delegate void Delg_OpsComplete(int StatusCode,string Message);
        private delegate void Delg_BroadCast(string Message,bool Success);
        private delegate void Delg_OpsFailed(int StatusCode, string Message);
        private delegate void Delg_OpsCancelled(int StatusCode, string Message);
        private delegate void Delg_OpsStarted(int StatusCode, string Message);
        private delegate void Delg_OpsPaused(int StatusCode, string Message);

        // CB = callBack
        private Delg_NotifyUser CB_NotifyUser;
        private Delg_OpsComplete CB_OpsComplete;
        private Delg_BroadCast CB_BroadCast;
        private Delg_OpsFailed CB_OpsFailed;
        private Delg_OpsCancelled CB_OpsCancelled;
        private Delg_OpsStarted CB_OpsStarted;
        private Delg_OpsPaused CB_OpsPaused;

    }


Here you could find most delegates being declared and thereafter being instantiated. Lets cut-short this way of writing delegates with fewer lines of code.


I decided to write a generic utility class for handling delegates. This made the task of declaring delegates much simplified. Here is the code for the GenericDelegate utility class.

public static class GenericDelegate
{
    public delegate void Delg_Mthd();
    public delegate void Delg_Mthd<P1>(P1 Val1);
    public delegate void Delg_Mthd<P1, P2>(P1 Val1, P2 Val2);
    public delegate void Delg_Mthd<P1, P2, P3>(P1 Val1, P2 Val2, P3 Val3);
    public delegate void Delg_Mthd<P1, P2, P3, P4>(P1 Val1, P2 Val2, P3 Val3, P4 Val4);
    public delegate void Delg_Mthd<P1, P2, P3, P4, P5>(P1 Val1, P2 Val2, P3 Val3, P4 Val4, P5 Val5);

    public delegate R Delg_Proc<P1, R>(P1 Val1);
    public delegate R Delg_Proc<P1, P2, R>(P1 Val1, P2 Val2);
    public delegate R Delg_Proc<P1, P2, P3, R>(P1 Val1, P2 Val2, P3 Val3);
    public delegate R Delg_Proc<P1, P2, P3, P4, R>(P1 Val1, P2 Val2, P3 Val3, P4 Val4);
    public delegate R Delg_Proc<P1, P2, P3, P4, P5, R>(P1 Val1, P2 Val2, P3 Val3, P4 Val4, P5 Val5);
}


The only limitation I came to see with GenericDelegate class was that, the developer won't be able to call parameters with Ref and Out keywords in their parameters. While the params keyword can be used with the only limitation that, you need to pass values as an array instead of passing as individual parameters. 


In the following code snippet you can see how the GenericDelegate class is being put to use, in place of creating delegates for each and every callback methods.

namespace DelegatesGeneric
{
public static class GenericDelegate
{
    public delegate void Delg_Mthd();
    public delegate void Delg_Mthd<P1>(P1 Val1);
    public delegate void Delg_Mthd<P1, P2>(P1 Val1, P2 Val2);
    public delegate void Delg_Mthd<P1, P2, P3>(P1 Val1, P2 Val2, P3 Val3);
    public delegate void Delg_Mthd<P1, P2, P3, P4>(P1 Val1, P2 Val2, P3 Val3, P4 Val4);
    public delegate void Delg_Mthd<P1, P2, P3, P4, P5>(P1 Val1, P2 Val2, P3 Val3, P4 Val4, P5 Val5);

    public delegate R Delg_Proc<P1, R>(P1 Val1);
    public delegate R Delg_Proc<P1, P2, R>(P1 Val1, P2 Val2);
    public delegate R Delg_Proc<P1, P2, P3, R>(P1 Val1, P2 Val2, P3 Val3);
    public delegate R Delg_Proc<P1, P2, P3, P4, R>(P1 Val1, P2 Val2, P3 Val3, P4 Val4);
    public delegate R Delg_Proc<P1, P2, P3, P4, P5, R>(P1 Val1, P2 Val2, P3 Val3, P4 Val4, P5 Val5);
}

    public class GenericDelegateDemo
    {
        GenericDelegate.Delg_Mthd DlgProc0;
        GenericDelegate.Delg_Mthd<string[]> DlgProc1;
        GenericDelegate.Delg_Mthd<string> DlgProc2;
        GenericDelegate.Delg_Proc<string,string,string> DlgProc3;

        public GenericDelegateDemo()
        {
            DlgProc0 = DisplayNothing;
            DlgProc1 = DisplayParams;
            DlgProc2 = Display;
            DlgProc3 = GetFullName;
        }

        public void InvokeDelegates()
        {
            DlgProc0();
            DlgProc1(new []{"Alpha","Beta"});
            DlgProc2("Hello");
            Console.WriteLine(DlgProc3("George","Allen"));
        }

        private void Display(string Message)
        {
            Console.WriteLine(Message);
        }

        private void DisplayParams(params string[] Message)
        {
            foreach (var s in Message)
            {
                Console.Write(s + ", ");
            }
            Console.WriteLine("");
        }

        private void DisplayNothing()
        {
            Console.WriteLine("Nothing");
        }

        private string GetFullName(string FirstName,string SecondName)
        {
            return FirstName + " " + SecondName;
        }
    }
}

You can see the code now looks much leaner, while declaring and instating delegates, which made me quite satisfied with the solution.


Microsoft Solution for Generic Delegates
While browsing through the MSDN documentation, I happened to see a similar kind of solution for the very issue we had just discussed. The solution comes with two different types of inbuilt delegates namely Action and Func. The Func being for creating delegates with return types, while the Action for delegates with void return type and with a single parameter.

Lets see how to put to use the Func and Action keywords for implementing Generic Delegates.


public class MSFTGenericDelegates
{
    Action DlgProc0;
    Action<string[]> DlgProc1;
    Action<string> DlgProc2;
    Func<string,string,string> DlgProc3;
    Func<string, bool> DlgProc4;

    public MSFTGenericDelegates()
    {
        DlgProc0 = DisplayNothing;
        DlgProc1 = DisplayParams;
        DlgProc2 = Display;
        DlgProc3 = GetFullName;
        DlgProc4 = IsValidInt;
    }

    public void InvokeDelegates()
    {
        DlgProc0();
        DlgProc1(new []{"Alpha","Beta"});
        DlgProc2("Hello");
        Console.WriteLine(DlgProc3("George","Allen"));
        Console.WriteLine(DlgProc4("1"));
    }

    private void Display(string Message)
    {
        Console.WriteLine(Message);
    }

    private void DisplayParams(params string[] Message)
    {
        foreach (var s in Message)
        {
            Console.Write(s + ", ");
        }
        Console.WriteLine("");
    }

    private void DisplayNothing()
    {
        Console.WriteLine("Nothing");
    }

    private string GetFullName(string FirstName, string SecondName)
    {
        return FirstName + " " + SecondName;
    }

    private bool IsValidInt(string IntVal)
    {
        int Result;
        return int.TryParse(IntVal, out Result);
    }
}

In the above code snippet, the Action delegate is used to declare delegates with for containing methods with parameters of void return types while the Func delegate for holding methods with return types as well as parameters. 


The Action and Func delegates behind the scenes implements generic delegates with multiple parameters, here you can see a screenshot of disassembled code from the Red-Gate .NET Reflector.






I wish, if I knew this inbuilt delegates beforehand, I could have saved some time on more productive tasks. Anyway I am glad to find my generic delegate solution similar to what Microsoft had provided.

No comments: