March 24, 2017

Demistifying C# Delegates

This blog post attempts to demystify the concept of delegates in C#.

Before we dive into programming, let’s look at the English word itself. The verb delegate has the following literal meaning.

Delegate : Entrust (a task or responsibility) to another person, typically one who is less senior than oneself.

Delegates in C# does exactly that, by creating a C# delegate in a class we can entrust an operation to be carried out by another party that uses the class.

Also a more technical definition would be.

A delegate is a function pointer which is type safe (type safety means that it can only point to a valid method with the same signature as itself)

Let’s look at a programming challenge one may face.

Imagine that we are creating an extendable framework for an e-commerce application. Assume that this e-commerce application has a class named; ShoppingCart This class needs to calculate the final tax on sale before checkout.

Tax calculation methods differ from time to time and state to state. How do you allow for users of your framework to extend the tax calculation logic with their own? This is one of the scenarios where delegates come in handy.

Look at the following code example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Delegates
{
    class Program
    {
        static void Main(string[] args)
        {
            var taxDelegate = new ShoppingCart.TaxCalcDelegate(TaxCalc);
            Console.WriteLine($"{ShoppingCart.CalculateCheckoutTotal(taxDelegate)}");
            Console.ReadKey();
        }

        public static decimal TaxCalc(decimal totalCost, decimal totalShipping)
        {
            // 2% of total and 1% of shipping as tax
            return totalCost * 0.02m + totalShipping * 0.01m;
        }
    }

    public class ShoppingCart
    {
        public delegate decimal TaxCalcDelegate(decimal totalCost, decimal totalShipping);

        public static decimal CalculateCheckoutTotal(TaxCalcDelegate taxCalc)
        {
            var totalCost = 324.0m;
            var totalShipping = 15.0m;

            var tax = taxCalc(totalCost, totalShipping);
            return totalShipping + totalCost + tax;
        }
    }
}

In the example above, the class ShoppingCart declares a public delegate named TaxCalcDelegate with a signature that takes in 2 decimal values (total cost and total shipping). We have also created a method called TaxCalc in our program with the same signature which does the tax calculation according to our custom logic (2% tax on goods and 1% tax on shipping).

During runtime, we are creating a TaxCalcDelegate instance (Line 12) and providing the TaxCalc method as our method to run.

When we execute the program and execution hits the line where CalculateCheckoutTotal is called, it will also run our method to calculate the tax.

With the above code, we have effectively delegated the task of calculating tax to the user of the framework.

Multicast Delegates

You can assign multiple methods to the same delegate instance, and when invoked, execution starts from first attached to the last attached in sequence. These types of delegates are called multicast delegates.

Let’s look at a code example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Delegates
{
    class Program
    {
        static void Main(string[] args)
        {
            var runDelegate = new Animal.RunDelegate(Walk);
            runDelegate += new Animal.RunDelegate(Run);
            runDelegate += new Animal.RunDelegate(RunFast);

            Animal.StartRunning(runDelegate);
            Console.ReadKey();
        }

        public static int Walk()
        {
            Console.WriteLine($"I am walking...");
            return 1;
        }

        public static int Run()
        {
            Console.WriteLine("I am running...");
            return 2;
        }

        public static int RunFast()
        {
            Console.WriteLine("I am running fast...");
            return 3;
        }
    }

    public class Animal
    {
        public delegate int RunDelegate();

        public static void StartRunning(RunDelegate run)
        {
            var returnVal = run();
            Console.WriteLine(returnVal);
        }
    }
}

In the above code. We have a class named Animal which has a delegate named RunDelegate. The StartRunning method takes in a delegate of type RunDelegate. Within our program, we have three methods called Walk, Run, RunFast. And we have attached these methods to the RunDelegate object that we have instantiated.

When the code executes StartRunning, the three methods mentioned earlier will all run in sequence one after another. and produce the following output.

I am walking...
I am running...
I am running fast...
3

Return Value of a Multicast Delegate

Notice that in the above example the return value is 3 when it executes the Run() method. This value is returned by the RunFast method.

In a multicast scenario where the delegate signature has a return type, if you follow the above approach, only the last executed method’s return value will be available during runtime.

If you wish to grab the return values of all the methods in the multicast delegate you can do the following.

public static void StartRunning(RunDelegate run)
{
    var runDelegates = run.GetInvocationList();
    foreach (var d in runDelegates)
    {
        var result = (int)d.DynamicInvoke();
        Console.WriteLine(result);
    }
}

This will output the following:

I am walking...
1
I am running...
2
I am running fast...
3

With the above example, you would have understood that you can execute individual methods by getting a list of methods attached to the delegate using GetInvocationList.

Event Delegates

Another scenario where delegates are useful is when programming for events.

Let’s look at the following code example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Delegates
{
    class Program
    {
        static void Main(string[] args)
        {
            WeatherMonitor notifier = new WeatherMonitor(22);
            
            // Subscribe to the event and output the temperature when it fires.
            notifier.TemperatureChanged += new EventHandler(TemperatureChangeHandler);
            notifier.Temperature = 22; // Does nothing, this is the same value
            notifier.Temperature = 20; // Outputs "20" because the event is raised.
            Console.ReadKey();
        }

        public static void TemperatureChangeHandler(object sender, EventArgs args)
        {
            var currentTemp = ((WeatherMonitor)sender).Temperature.ToString();
            Console.WriteLine($"Temperature changed to {currentTemp} degrees.");
        }
    }

    public class WeatherMonitor
    {
        private int temperature;

        public WeatherMonitor(int currentTemperature)
        {
            temperature = currentTemperature;
        }

        public event EventHandler TemperatureChanged;

        public int Temperature
        {
            get { return this.temperature; }
            set
            {
                // If the value has changed...
                if (temperature != value)
                {
                    // Assign the new value to private storage
                    temperature = value;

                    // And raise the event (if TemperatureChanged is not null)
                    TemperatureChanged?.Invoke(this, EventArgs.Empty);
                }
            }
        }
    }
}

The WeatherMonitor class in the above example declares a public event delegate named TemperatureChanged. The program “subscribes” to the event delegate by creating an event handler called TemperatureChangeHandler. Every time the temperature property changes, it triggers the TemperatureChanged Event.

You can also simplify the syntax by using a lambda expression for attaching an event delegate like the following example.

        static void Main(string[] args)
        {
            WeatherMonitor notifier = new WeatherMonitor(22);
             
            // Subscribe to the event and output the temperature when it fires.
            notifier.TemperatureChanged += (s, e) => Console.WriteLine($"{notifier.Temperature.ToString()}");
            notifier.Temperature = 22; // Does nothing, this is the same value
            notifier.Temperature = 20; // Outputs "20" because the event is raised.
            Console.ReadKey();
        }

Hope I have brought some clarity to the concept of Delegates in C#. See my other posts for more on C# programming concepts.

Related posts: