Functional programming with C# - Delegates

One of the core concepts of functional programming is functions and the ability of using, manipulating and passing functions as if they were objects. C# delegates allows us to create types to store functions with a specific signature.

In C# we have simple types such as bool, byte, decimal, int, char etc. and others such as object and string.

Some people might think of functions (also called methods, same thing) as a part of a class, but in C# you can use functions as if they were independent objects thanks to delegates. Let’s see it step by step. Suppose that your boss asks you to implement a program that displays in console the result of the division of two numbers unless the denominator is zero. Easy task! you could have something like this:
The above code is straigtforward and would display The result of the division is 2 because 6 / 3 = 2. Notice that when the denominator is zero instead calculating the division (which would result in a program exception) it will write in console that it cannot divide by zero.


Delegates

Now let's imagine that the boss ask you to have the divide functionality in a separate smart class, and sometimes the message should be written on console, but some other times it should be sent by email or written in a file. Where exactly the message is printed should not be the smart class' concern.

What our boss is telling us is that the smart class' divide method single responsibility is to divide and write a message with the result. Where to write this message? It doesn't matter, wherever the program using its divide functionality decides. In other words, the divide method should know how to divide numbers but should delegate the message printing responsibility onto whatever way the main program decides. Let's see a solution separating concerns.
In Program.cs
and in SmartClass.cs
Microsoft defines delegates as a type that represents references to methods with a particular parameter list and return type. In other words, a delegate is a type to store functions or methods, but not any method, only the ones that have the same signature (same number and type of parameters and same return type).

Actually we should say that the delegates are types to store the memory address of methods with a specific signature.

If this definition is not clear enough let’s analyze the example and then read again the definition. The class Program is simply our entry point. It has the main method that simply instantiates DelegateExamples and calls the method RunExampleDelegate. The interesting part is where the SmartClass has now a public definition of a delegate called WriteMessage. Let's remember the definition and analyze this delegate. WriteMessage is a new type to store memory addresses of methods that return void (nothing) and accept one argument of type string. That's all. We have now a new type that we could use similar to string, object, int, float, etc. with the difference that this type instead being used to store words, or objects, or numbers, or numbers with decimals is used to store functions that accept a string as input and have no output.

We have a new type, the delegate WriteMessage, great! but now we have to use it. So the SmartClass has also a property called MethodAddress of type WriteMessage. This property is a placeholder to store methods with the delegate's signature. Again, think of it in the same way as a string property being a placeholder to store text, but a WriteMessage property will store methods with the specific signature described before.

The Divide method still knows how to divide. The difference is that instead printing in console the result, it now delegates this task to the method (or methods) stored in the MethodAddress property if it's not null.

In DelegateExamples you can see that before invoking the smart class' divide method it is setting a reference to the method WriteInConsole (the address in memory of that method) in the MethodAddress property. In other words it is specifying where to delegate the printing responsibility. On this example the method WriteInConsole is simply printing the message in console, but we could use other methods that do something else with the message.

Finally one important thing to notice is the way we set methods into delegate types. We could do smartClass.MethodAddress = WriteInConsole; but remember we have said that a delegate store method or methods? That's right, we can use the MethodAddress property to store many different methods (i.e: many different references to methods or addresses in memory) and that's why we use the += way of assigning as if we were to concatenate multiple values smartClass.MethodAddress += WriteInConsole;. Do the test, assign more than one reference to a method with the same signature or even assign the WriteInConsole method reference twice and you will see that its code is also executed twice!

NOTE: If you are familiar with interfaces and how to use interfaces to, for example, implement the strategy pattern you will understand the following statement: Interfaces are to classes as delegates are to methods. In fact any problem that can be resolved with delegates can be resolved with interfaces.

Find the examples on my GitLab repository

Comments