Delegates Plug-in Methods and Multicast Delegates

Delegates Plug-in Methods and Multicast Delegates

What are Delegates?

Delegates are a C# Type that represents functions. A delegate is defined with a return type and list of parameters which is important as we will see later.

Delegates are used to pass functions to a different method, this method, for example, may reside within a class. You can define a delegate in the following manner:

public delegate void CustomReport(int x);

This code will create a public delegate that has the return type of void (nothing) and a single integer parameter named x. Now that we have this delegate, we can associate any method that has the same return type and parameters with it, this will allow us to ‘plug-in’ new code to existing classes.

Delegate and plug in example
Associating delegates with functions

A very useful and common use for delegates is to define callback functions. Callback functions are called after some other action has been completed. For example, in machine learning, you may want to call a function every time you pass through your dataset (epoch).

How to Use a Delegate

We will go through an example of how a delegate might be used when passing a function to a member method of a different class.

First, we need to define the delegate as we did earlier. This means you need to specify the return type and parameters for the delegate:

public delegate string FruitCount(int x);

Next, we, need to write one or more functions that satisfy the structure set out by the delegate, in this case, we need a function that returns a string and takes an integer as a parameter. The name of the parameters does not need to match as long as their types do. Here I define a class NewFunctionsClass that contains two variants of the same method.

public class NewFunctionsClass
{
    public string fruitCounter(int x)
    {
        return $"number of pieces of fruit is {x} : Method 1";
    }

    public string fruitCounter2(int x)
    {
        return $"number of pieces of fruit is {x} : Method 2";
    }
}

Next, we need somewhere that will consume the delegate. In this scenaio I create a fruit class. This fruit class contains both the delegate itself and the method that is intended to use it.

class Fruit
    {
        public delegate string FruitCount(int x);

        public void FruitLogger(FruitCount f, int fruitCount)
        {
            Console.WriteLine(f(fruitCount));
        }
    }

The FruitCount method is very simple, it takes the delegate and an additional integer value. It then outputs the result of the delegate function to the console. The method will only accept functions that have the return types and parameters as the FruitCount delegate. The fruitCounter and fruitCounter2 functions satisfy these requirements, therefore, they can be used in this context.


                    using System;

    class Program
    {
        public delegate string Del(int message);

        static void Main(string[] args)
        {
            NewFunctionsClass newFunctions = new NewFunctionsClass();
            Fruit fruit = new Fruit();

            fruit.FruitLogger(newFunctions.FruitCounter, 5);
            fruit.FruitLogger(newFunctions.FruitCounter2, 5);
        }
    }

    public class NewFunctionsClass
    {
        public string FruitCounter(int x)
        {
            return $"number of pieces of fruit is {x} : Method 1";
        }

        public string FruitCounter2(int x)
        {
            return $"number of pieces of fruit is {x} : Method 2";
        }
    }

    class Fruit
    {
        public delegate string FruitCount(int x);

        public void FruitLogger(FruitCount f, int fruitCount)
        {
            Console.WriteLine(f(fruitCount));
        }
    }

                

In this example, we call the Fruit.FruitLogger function twice, once passing newFunctions.FruitCounter as a candidate for the delegate and the other time passing newFunctions.FruitCounter2. You can see how this change impacts the output of the program.

Combining Delegates (Multicast Delegates)

One interesting property of a delegate is that multiple objects can be assigned to them by using the addition operator. The result of this addition is a delegate that has multiple methods contained as a list. When the delegate is used, all of the methods that are stored are invoked. All methods assigned to the delegate must still conform to the requirements of the return type and the parameters.

For example, multiple methods assigned to a delegate would look like the following:


                    using System;

public class Program
{
	delegate void CustomDel(int s, int y);

 static void DoubleNumber(int x, int y)
    {
	 	int doubleValue = checked(x*2);
        Console.WriteLine($"Doubled value is {doubleValue} and this was part of delegate number {y}");
    }

    static void TripleNumber(int x, int y)
    {
		int tripleValue = checked(x*3);
        Console.WriteLine($"Tripled value is {tripleValue} and this was part of delegate number {y}");
    }

    static void Main()
    {
        CustomDel doubleNumber, tripleNumber, multiDel, multiMinusMultiplyDel;
        doubleNumber = DoubleNumber;
        tripleNumber = TripleNumber;
        multiDel = doubleNumber + tripleNumber;
        multiMinusMultiplyDel = multiDel - tripleNumber;

        doubleNumber(2,1);
        tripleNumber(2,2);
        multiMinusMultiplyDel(2,3);
		multiDel(2,4);
    }
}
                

If the delegate has a non void return type, the result of the last method in the delegate will be assigned to the variable on the left.

More can be learnt about multicast delegates here https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide

Instance Methods, Generics and Delegates

Both instance and static methods can be assigned to a delegate. One significant difference between the two is that a delegate that has an instance method assigned to it will always keep reference to the instance of a class it belongs to. This object can be accessed via the ‘Target’ property of the delegate.

Additionally, the delegate type can support generic type parameters. This means that as long as the method has the same number of parameters, a method can be written that accepts any type.

Due to these delegates being so general, there are already definitions for them in the system namespace. The Func delegate is already defined and allows for a single argument to be provided and return an object of the same type.