Saturday, April 27, 2024 06:55

Table of contents >> Delegates, Lambda Expressions, Events > Delegates

Delegates

You already know from the lesson methods and functions and parameters that you can create methods that accept a number of parameters of different types. But what if you would want to send a method itself as a parameter to another method? Delegates allow us to do exactly that.

I will try to walk you step by step through the concepts that describe them, since they are a rather advanced topic. So, first of all, lets create a simple method that displays some text on the screen, and then call it:

This is as simple as it gets:

Calling a method in C#Now, I will add a new delegate declaration:

The first odd thing you should notice is the fact that my delegate is outside the Program class. In fact, it’s even outside the namespace. This is because when the compiler outputs the MSIL (Microsoft Intermediary Language), when it encounters my delegate declaration, it actually converts it to a class:
Of course, we know that classes can be declared outside namespaces, and most of the times, they are declared inside a namespace. But, wait! In the lesson nested classes, we learned that we can declare classes inside other classes. Can we also declare delegates inside other classes? Yes, we can, since delegates are also classes, behind the scenes.

Of course, since a delegate is a class, and a class can be instantiated, this means that we can also instantiate our delegate:

You will notice that you will actually get an error in Visual Studio: Error CS1729 ‘FooDelegate’ does not contain a constructor that takes 0 arguments. If you will hover the mouse cursor over the instantiation parameter you will see that the signature is FooDelegate.FooDelegate( void () target). So, it expects a parameter named void () target, which is a syntactic way of telling us that it expects a method that returns void and has no arguments. It so happens that we have just a method that returns void and takes no arguments: Foo(). So, I will pass it to the constructor of my instantiated delegate.:

Pay attention that I wrote new FooDelegate(Foo);, not new FooDelegate(Foo());. That’s because I’m not invoking the method, I’m passing it.

Next, take a look at this:

You know that fooDelegate is a reference, because, as I explained earlier, its type, FooDelegate, is actually a class. So, how come I am able to write fooDelegate() ? How can I treat an instance as a method? Because, if you’ll run the program at this stage, you’ll get the same result as in the first screenshot, the Foo() method will be called.

We are dealing again with some help from the compiler, in the form of syntactic sugar, because behind the scenes, the compiler replaces fooDelegate() with fooDelegate.Invoke(). This means that when it replaces our delegate with a generated class, it places inside it a method named Invoke().

This is actually a bit funny, in a way, fooDelegate references Foo(). And the compiler can do even more than that. We can even reference the Foo() method directly, which is called treating Foo() as a first class object, meaning that we are treating it as an object, we have a reference to it :

and the compiler would also replace FooDelegate fooDelegate = Foo; with FooDelegate fooDelegate = new FooDelegate(Foo);. The next logical step is the fact that we can also use delegates as parameters of other functions:

And now, we can pass fooDelegate to it:

And, what do you know? I’m actually passing a function as a parameter to another function. We could declare another void method which accepts no parameters, named Goo(), and pass that one too as a parameter to InvokeDelegates().

I will use a Visual Studio extension called ILSpy to have a look at the intermediary language of the above program. If I scroll down to FooDelegate, I can actually see the class that the compiler generated in place of my delegate. It looks like this:

MSIL (Microsoft Intermediary Language) generated for a delegateYou don’t actually need to learn about reading intermediary language, but you can notice that the compiler generated a class named FooDelegate, just like my delegate, and this class extends (inherits from) a class named MultiCastDelegate. If I click on the MultiCastDelegate declaration, I will see that it also inherits from System.Delegate.

Lets further analyze the code of the class that the compiler generated. It has a constructor that takes as parameters an object and an int, and three other methods: BeginInvoke(), EndInvoke() and Invoke() (which returns void and takes no arguments, just like our delegate). If we declared our delegate as delegate void FooDelegate(int someValue);, the signature of the Invoke() method would also change to take an int parameter.

A method in C# is pretty much an address, a pointer to an address in the RAM memory, where the execution jumps when we execute the codes inside that method. So, basically, in the constructor of the class generated for the delegate, the int parameter just stores the RAM address where the method that we pass to the delegate is located. The object parameter is the object in which to invoke the method upon. When I declared the Foo() method, I declared it as static, which means no object is associated with it, so the object parameter of the constructor will be null. But, let’s say I’d create another method, which is not static:

Obviously, in order to use a non static method, I first need to make an instance, to which the method belongs to, an object associated with it. Therefor, I declared a new Program class instance called prog, and then i was able to pass prog.Goo to the delegate. In this case, the object parameter of the constructor receives the prog instance.

There are two important properties that the delegate has:

Properties of delegates in C#

If I modify the codes to display their values, for both the static and the non static methods, like this:

I will receive this output:

Output of Method and Target delegate propertiesSo, notice that for the first delegate instance we get only the name of the method assigned to it, Foo(), and no object, because it’s static and has no object associated with it, while for the second delegate instance, we get the name of the Goo() method, but also the name of the class instance to which Goo() belongs.

Method is a property that holds a reference to the method assigned to the delegate, while Target is a reference to the object to invoke the method upon, or to which the assigned method belong to.

After all this talk, the obvious question comes: why use delegates? We could just invoke the method assigned to them directly, without declaring an extra delegate. And, just like in the case of interfaces, the answer might not be obvious at first sight. Delegates, fore and foremost, allow us to parameterize code.

For instance, take this example:

By using the _number parameter, we are able to pass any integer to the method, and find its square number. This is parameterization. With delegates, instead of integer, or string, or even class instances, we are allowed to pass code, or, at least a reference to some code, because, that’s what delegates are, they are references to some code.

We have this code:

in which I declared a static method named GetAllNumbersLessThanFive. It accepts an IEnumerable<int> generic interface parameter, to which we are sending an array of numbers. Implementing the IEnumerable interface makes us able to use a foreach iteration. I will explain IEnumerable in a future lesson. For now, just know that our method is returning all the numbers that are passed to it as parameters, and are less than five.

The above code will output this:

Output of IEnumerable interfaceNow, what if we’d need a method that displays the numbers less than 10? Obviously, the wrong and fastest way to solve it would be to copy and paste the existing method, modifying the condition to filter numbers less than 10. What if I need another method that filters numbers less than 13? And then 20? This would soon become painful, we would end up with a ton of methods that are literally the same. The only thing that differs is the number in the conditional check. A quick fix would be to add another parameter to the method, and pass the number we want to check through it. But, fundamentally, that doesn’t solve the problem entirely. What if we suddenly decide that we want a method that gives us all the numbers greater than 5? In that case, we wouldn’t be able to pass the number as a parameter, since we would also need to change the check expression from less than to greater than. What we would need would be to parameterize this code: _number < 5. And this is a perfect place for delegates to do it.

So, analyzing the expression we want to parameterize, we observe that we need a delegate that points to a method that accepts a number as a parameter, which will be the number to be checked, and returns a boolean, indicating whether the number is less than the value or not. Lets declare this delegate and pass it as a parameter to the GetAllNumbersLessThanFive method:

First, I declared a delegate named FilterDelegate, which accepts a method that takes an int as a parameter, and returns a boolean. Then, I declared thee of such methods: LessThanFive, LessThanTen, GreaterThanThirteen. I then renamed the GetAllNumbersLessThanFive method as RunNumbersThroughFilter, since now it is parameterized, and it performs multiple checks, not just less than, because I also added a FilterDelegate delegate parameter, to which I will pass methods that will perform various checks. Inside it, in the conditional check, I am using this method to actually perform the filtering: if(_filter(_number)). Finally inside the Main method, I am calling the RunNumbersThroughFilter method, passing the numbers array to it, and one of the three methods as the method for filtering.

When I run this code, the _numbers array will be passed to the RunNumbersThroughFilter method, which will start iterating over all of them. During this iteration, I am performing a conditional check inside which I am using the method referenced by the delegate parameter. The method will execute its code, performing the actual conditional check that performs the filtering. The result will look exactly the same as the first time I filtered the numbers, without using a delegate. The difference is that now, I can use another method to filter them:

Since now I used the GreaterThanThirteen, I will get this result:

True, I still have some kind of copy and paste codes, with those three filtering methods, but still, using a single method that accepts any kind of filtering, made things a lot better. In the next lesson, I will explain how we can improve this even further, by using lambda expressions.

Tags: , , ,

6 Responses to “Delegates”

  1. mosaid says:

    keep up the good work rusoaica 🙂 , I will certainly keep this site in mind when learning c#

  2. qubitz says:

    Great job keeping this tutorial simple and focused! So many other tutorials are cluttered with unrelated advanced concepts. I’ve been using delegates for years and never knew they were classes, lmao.

  3. SirPi says:

    I think…. i understand. AFTER 10000 years!

    • rusoaica says:

      It is only difficult when you try to understand without following all the steps. I’m glad you understood!

Leave a Reply



Follow the white rabbit