Monday, 15 June 2015

Delegate Functions

If you google the word delegate you'll find "a person sent or authorized to represent others, in particular an elected representative sent to a conference." Which isn't a half bad definition. In a c# context; a delegate can be thought of as a strongly typed variable that holds a reference to a static function.

You can think of a delegate as 'a defined function variable' with signature and return type. This delegate type can be used to define multiple delegate handlers,


namespace pav.delegateExample;

class Program
{
    // create our delegate types
    delegate int mathDelegate(int x, int y);
    delegate string combineDelegate(int x, int y);

    static int Add(int x, int y)
    {
        return x + y;
    }

    static string Combine(int x, int y)
    {
        return $"{x} {y}";
    }

    static void Main(string[] args)
    {
        //instantiate handlers of type mathDelegate
        mathDelegate addHandler = Program.Add;
        mathDelegate subHndler = (int x,int y) => x-y;

        Console.WriteLine(addHandler(3, 4));
        Console.WriteLine(subHndler(3, 4));


        // Delegates are strongly typed refrence points in
        // memory for functions, because the combine function
        // has a differnt return type, we cannot reuse the
        // mathDelegate.
        combineDelegate combineHandler = Program.Combine;

        Console.WriteLine(combineHandler(3, 4));
    }

}


Above is  simple example that demonstrates a variable "handler" of type mathDelegate which we assign static functions to, we can then invoke those function by calling our delegate handler and pass the appropriate parameters.

Now what use could this possibly be? well it's a nice way of allowing objects to pass in their own functions or methods into other objects. Take a look at the example below.


namespace pav.delegateExample;

class Program
{
    struct Employee
    {
        public string FirstName;
        public string LastName;

        public Employee(string FirstName, string LastName)
        {
            this.FirstName = FirstName;
            this.LastName = LastName;
        }
    }

    delegate void FormatEmployee(Employee Employee);

    class EmployeeService
    {
        public List<Employee> Employees { get; } = new List<Employee>() {
                new Employee("Pawel","Chooch"), new Employee("Steve","Smith"),
                new Employee("Ian","Price"), new Employee("Tomek","Wan"),
                new Employee("Jacques","Cocion") };

        public void ProcessEmployee(FormatEmployee dFormat)
        {
            foreach (var e in Employees)
                dFormat(e);
        }
    }

    static void PrintEmployee(Employee e)
    {
        Console.WriteLine($"{e.FirstName} {e.LastName}");
    }

    static void PrintEmployeeInitial(Employee e)
    {
        Console.WriteLine($"{e.FirstName.Substring(0, 1)}. {e.LastName}");
    }

    static void Main()
    {
        var es = new EmployeeService();

        es.ProcessEmployee(PrintEmployee);
        es.ProcessEmployee(PrintEmployeeInitial);
    }
}


Above our EmployeeService class has a collection of employees and a Process Employee Function, by leveraging a delegate as the function's parameter, our application can decide what the ProcessEmployee method will do.

The idea is to decouple our application, the class shouldn't be writing to consoles or files, but instead should be taking in functions that provide this functionality. First let's start by adding windows forms to our console application, open the .csproj file and paste the following.


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>


In the above we add the UseWindowsForms  node and modify the TargetFramework one, this will let us use windows forms in our console application. 

namespace pav.delegateExample;

class Program
{
    struct Employee
    {
        public string FirstName;
        public string LastName;

        public Employee(string FirstName, string LastName)
        {
            this.FirstName = FirstName;
            this.LastName = LastName;
        }
    }

    delegate string FormatEmployeeDelegate(Employee Employee);
    delegate void WriteNameDelegate(string value);

    class EmployeeService
    {
        public List<Employee> Employees { get; } = new List<Employee>() {
                new Employee("Pawel","Chooch"), new Employee("Steve","Smith"),
                new Employee("Ian","Price"), new Employee("Tomek","Wan"),
                new Employee("Jacques","Cocion") };

        public void ProcessEmployee(FormatEmployeeDelegate dFormat, WriteNameDelegate dWrite)
        {
            foreach (var e in Employees)
                dWrite(dFormat(e));
        }
    }

    static string PrintEmployee(Employee e)
    {
        return $"{e.FirstName} {e.LastName}";
    }

    static string PrintEmployeeInitial(Employee e)
    {
        return $"{e.FirstName.Substring(0, 1)}. {e.LastName}";
    }

    static void WriteToConsole(string value)
    {
        Console.WriteLine(value);
    }

    static void WriteToMessageBox(string value)
    {
        MessageBox.Show(value);
    }

    static void Main()
    {
        var es = new EmployeeService();

        es.ProcessEmployee(PrintEmployee, WriteToConsole);
        es.ProcessEmployee(PrintEmployeeInitial, WriteToMessageBox);
    }

}



Above we changed our delegate to now return a string, this string will be the transformation we want to make onto our Employee, and the second delegate will be in charge of writing the value somewhere. In our main we pass two different formats and two different ways of writing. One to the console and the other with a message box. Now the EmployeeService class only focuses on employees and accepts delegates that define 'how to format text' and 'where to output it' as parameters.

you can read a bit more on delegates on MSDN: delegates

a delegate can call more then one method, this is referred to as multicasting. All we have to do is refactor our process employee method to call each function in the invocation list.

       
public void ProcessEmployee(FormatEmployeeDelegate formatEmployee,
                            WriteNameDelegate Callback) {
    if (formatEmployee != null && Callback != null)
        foreach (FormatEmployeeDelegate d in formatEmployee.GetInvocationList())
            foreach (var e in Employees)
                Callback(d(e));
}


with that done if we run our application since currently we are only passing one delegate to the ProcessEmployee method we won't see any difference whatsoever from our previous code, so what we have to do is add multiple functions to our delegate. we can do this by using the same syntax we'd use for events. += to add and -= to remove.

   
    static void Main()
    {
        var es = new EmployeeService();

        FormatEmployeeDelegate fe = PrintEmployee;
        fe += PrintEmployeeInitial;

        es.ProcessEmployee(fe, WriteToConsole);
    }


What we did to our main is declared a Format Employee delegate fe and made it equal to our PrintEmployee function, then appended the PrinteEmployeeIntitial function to it. We can see that if we pass our fe delegate with two function they both fire, and if we just pass the one only it fires.

if we want, we can subtract our functions with the -= operation.

   
    static void Main()
    {
        var es = new EmployeeService();

        FormatEmployeeDelegate fe = PrintEmployee;
        fe += PrintEmployeeInitial;

        es.ProcessEmployee(fe, WriteToConsole);

        //remove format delegates
        fe -= PrintEmployee;
        fe -= PrintEmployeeInitial;

        es.ProcessEmployee(fe, WriteToMessageBox);
    }


Now our second call to the ProcessEmployee method does nothing, because the fe delegate contains no functions to call.

The take away from this is that delegates store references to methods or functions, they allow you to pass functions as parameters into functions or methods. One thing to keep in mind is that you'll only really see them used in legacy code, for the most part in modern applications you'll leverage the Func delegate.