DEV Community

Yuzhou Guo
Yuzhou Guo

Posted on • Updated on • Originally published at w26.one

Delegates in C#… why we need them?

So… you’re kinda stuck on Delegates?

No worries!

Delegates are always a bit hard to start with since we need some abstraction and encapsulation ideas to fully understand this concept, especially as beginners having to absorb everything about Object-Oriented Programming (OOP) from scratch.

In the world of Object-Oriented Programming, it is very important to understand how methods and classes interact with each other and share data/call back behind the scene.

To get further on these kinds of topics, one must know what is an Event in C# and to understand what is an Event, one must start with Delegates.

Assuming you have some basic knowledge about OOP, meaning that you know what is a class and what is an interface, this article will mainly focus on what is a delegate, how to use delegates in C# and what kind of benefits we can get from using that. In other words, why we need delegates in this language?


1. Delegate

According to the Microsoft documentation:

A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.

Okay, this is a bit overwhelming…

Let’s take a step back and think about what is essentially a method? Furthermore, what is a variable?

A variable is just the name of a particular location in memory storing some zeros and ones. With methods, this is still the same thing: a method name, aka the memory address label, points to/refers to/holds several lines of code.

So what is a delegate? At this point, we can say that a delegate is a function pointer that holds references of methods with a particular parameter list and return type.

To further understand this, let’s look at some real-life examples.

Here is a method receives a string and returns that string in all lower cases:

    string MessageToLower(string message) {

        Console.WriteLine("From MessageToLower!");

        return message.ToLower();

    }

We can see that this method takes one parameter of type string, and return a value of type string as well.

Now during the process of building our software, we realized that actually another functionality of converting all characters to all upper cases is also needed!

    string MessageToUpper(string message) {

        Console.WriteLine("From MessageToUpper!");

        return message.ToUpper();

    }

At this point, we sort of sense that maybe even more similar functionalities will be needed in the future… To makes things neat and well encapsulated, a delegate type shall be created to provide a layer of abstraction and readability.

You can also go the other way around, create the delegate first then methods if you’ve already known certain methods in your application should be wrapped together.

Alright back to implementation, here is how the delegate signature looks like:

    public delegate string ModifyAMessage(string message);

Notice that all types and number matches with the method: one parameter of type string, and return a value of type string. This is what we mean by having the same signature.

The name of the delegate is all up to you to be intuitive and meaningful. To represent an abstraction of “methods that modify the parameter message”, we decide to call it ModifyAMessage.

To execute a concrete method, we first need to instantiate the delegate and pass the method as a parameter:

    ModifyAMessage modifier = new ModifyAMessage(MessageToUpper);

Notice that we are not invoking this method directly, but rather pass the method itself as a parameter to the delegate object.

You can also do:

    ModifyAMessage modifier = MessageToUpper;

The C# compiler will immediately figure out what you mean.

Now the delegate object modifier points to the reference of the method MessageToUpper, so we just directly pass the string parameter in:

    var upper = modifier("hello there!");

We should be seeing From MessageToUpper! in the console and the value of the variable upper should be HELLO THERE!.

Notice that while invoking, the method can be different every time since we can assign different methods to the delegate type. The flexibility provided is exactly why we use a delegate in this case.

Method 1, 2, 3 all have the same signature thus Delegate_A(with the same signature) can represent the references of them.Method 1, 2, 3 all have the same signature thus Delegate_A(with the same signature) can represent the references of them.

In our case, methods can be: “convert to lower cases”, “convert to upper cases” and “remove punctuations”In our case, methods can be: “convert to lower cases”, “convert to upper cases” and “remove punctuations”

An overview of our implementation:

    public delegate string ModifyAMessage(string message);

    public void Calling() {

        ModifyAMessage modifier = MessageToUpper; 
        // choose to invoke the method "MessageToUpper"

        var upper = modifier("hello there!"); 
        // "From MessageToUpper!" in the console
        // "HELLO THERE!" as the value of the variable upper

    }

    string MessageToLower(string message) {

        Console.WriteLine("From MessageToLower!");

        return message.ToLower();

    }

    string MessageToUpper(string message) {

        Console.WriteLine("From MessageToUpper!");

        return message.ToUpper();

    }

Now go back and take a second look at the Microsoft official definition of the delegate type, it’s making so much more sense, right?


2. Multi-cast Delegate

Other than assign/update one single method reference to a delegate at a time, guess what, we can accumulate them!

We have something called multi-cast delegates that allow you to call multiple methods at the same time! Essentially, it is a function pointer that can point to more than one method with identical signatures.

Just like how we do x += y in C# or any other languages, what we are doing is taking the value from the variable x, increment it by the value of y, then put the result back to the variable x.

Well, the same thing going on with this multi-case thing! You first instantiate a delegate type, and then you can accumulate other methods onto it, or subtract methods from it:

    public void Calling() {

        ModifyAMessage modifier = MessageToUpper;
        modifier += MessageToLower;
        modifier += MessageToUpper;
        modifier -= MessageToUpper;
        // essentially, we are calling "MessageToUpper" and 
        // "MessageToLower" all together

        var result = modifier("hello there!");

    }

    string MessageToLower(string message) {

        Console.WriteLine("From MessageToLower!");

        return message.ToLower();

    }

    string MessageToUpper(string message) {

        Console.WriteLine("From MessageToUpper!");

        return message.ToUpper();

    }

Here is the console output:

    From MessageToUpper!
    From MessageToLower!

The value of the variable result will be the latest update from the method MessageToLower, thus its value would be hello there!.

You can actually receive all return values from all methods by using some tricks, but since this article mostly targets beginner programmers we won’t talk about that in detail. Feel free to read it here!

In this way, we can wrap different functionalities into a comprehensive method object and call multiple methods at the same time.

Note that delegate instances are immutable. So, when you combine them or subtract one delegate instance from the list, a new delegate instance is created to represent the updated or new list of the targets or methods to be invoked.


3. Delegate with call-back

At this point, you might be asking yourself why do we need a delegate type here? It seems like just calling the methods themselves can also do the job? Yeah, we can give the abstraction a better name, call a bunch of methods at a time and all that, but seems like a delegate type isn’t really a must-do?

Now let me show you something more interesting.

Another reason why people use delegates a lot is that it is very handy to use in the cases of call-back methods and set up a little notification system.

Back to the delegate assignment line we’ve done before:

ModifyAMessage modifier = MessageToUpper;

Let’s take one step further: since the delegate type just acts as a pointer and holds the references of methods, we can assign the method reference from anywhere, right?

Like from another method, from the parameter from another method, or even from a brand new class?!

We will stay with the message example, and for simplicity, we changed the method MessageToLower to a void method:

using System;

namespace delegateExplain
{
    class Program
    {
        static void Main(string[] args)
        {
            fromClient client = new fromClient();
            client.doingModifyJob();
        }
    }

    class fromClient
    {
        public void doingModifyJob()
        {
            string[] strings = { "apple", "banana", "my favorite pineapple" };
            for (int i = 0; i < strings.Length; i++)
            {
                MessageToLower(strings[i]);
            }
        }

        static void MessageToLower(string message)
        {
            Console.WriteLine("Processing..." + message);
        }
    }
}

What we were doing is that, inside the class Program, we created a class object client from the fromClient class and called the non-static method doingModifyJob on it.

The output on the console is:

    Processing...apple
    Processing...banana
    Processing...my favorite pineapple

So nothing fancy here, just calling a method from another class.

Now, what if we want the method doingModifyJob to report to us how everything’s going while it’s doing the job? Maybe this string is incredibly long and we would like to observe the processing status?

And also, to better organize the code, we shall have only the user/client input properties and related method within the fromClient class. All the methods that operate on the inputs, such as MessageToLower and MessageToUpper should be placed inside the Program class.

So we reorganized the code a bit:

using System;

namespace delegateExplain
{
    class Program
    {
        static void MessageToLower(string message)
        {
            Console.WriteLine("Processing..." + message);
        }

        static void Main(string[] args)
        {
            fromClient client = new fromClient();
            client.doingModifyJob();
            // How are we gonna use "MessageToLower"
            // inside "doingModifyJob" ???
        }
    }

    class fromClient
    {
        public void doingModifyJob()
        {
            string[] strings = { "apple", "banana", "my favorite pineapple" };
            for (int i = 0; i < strings.Length; i++)
            {
                Program.MessageToLower(strings[i]);
                // trying to do "Program." but even with this, 
                // the method "MessageToLower" is inaccessible due to its protection level
            }
        }
    }
}

Wait… but we are in the main method of the Program class, right? How are we going to use the internal method MessageToLower inside doingModifyJob which is from another class?

We can assign the method to a delegate type!

First, we provide the doingModifyJob method a delegate type parameter:

    public delegate void ModifyAMessage(string message);
    // create the delegate type, make sure the signature matches

    public void doingModifyJob(ModifyAMessage modifer){
        ...
    }

And we use just use modifer other than any concrete method:

using System;

namespace delegateExplain
{
    class Program
    {
        static void MessageToLower(string message)
        {
            /* may have some string operations here */
            /* not showing for demonstration simplicity */
            Console.WriteLine("Processing..." + message);
        }
        static void Main (string[] args)
        {
            fromClient client = new fromClient();
            client.doingModifyJob(MessageToLower);
            // pass the method as a parameter
            // we are essentially doing 
            // "ModifyAMessage modifier = MessageToLower"
        }
    }

    class fromClient
    {
        public delegate void ModifyAMessage(string message);

        public void doingModifyJob(ModifyAMessage modifier)
        {
            string[] strings = {"apple", "banana", "my favorite pineapple"};
            for(int i=0; i<strings.Length; i++)
            {
                modifier(strings[i]);
            }
        }
    }
}

And you can easily see the flexibility there: we can pass any method to the delegate object as long as the signature matches.

Try to implement something similar with a much larger string array and see how the method reports to you in the console window!


Conclusion

So here are all the reasons why we love delegates:

  • Delegates hold references of methods with the same signature so that we can assign a particular method to the delegate type when we needed.

  • Delegates allow us to wrap functionalities from different methods altogether (both accumulation and subtraction).

  • Delegates can be used to set up a notification system in the software such that methods/classes can communicate with each other.

In the next article, we are going to talk about what are EventHandlers. They are nothing more than methods that are invoked through delegates. See you all there!

Hello! My name is Yuzhou and I’m a student now studying computer science at Montreal. If you like this article and have an interest get in touch with me, please feel free to find me on my Linkedin and talk to me there :))

Latest comments (3)

Collapse
 
dyagzy profile image
dyagzy

Thank you Yuzhou for this beautiful article. You have helped me to better understand delegates. The last line and last code, how is it possible that the instance of the delegate (modifier) is able to accept a strings of array rather than a string since the methods it's pointing to accepts strings as parameters? Can you explain please?

Collapse
 
dmarinnaa profile image
dMarina

wow . this explanation is excellent!! thanks

Collapse
 
anssamghezala profile image
Anssam Ghezala

Great read :) Now I know how to properly use delegates!