DEV Community

Abdul
Abdul

Posted on

C# Delegates 🚚📦🏠

Heya good peeps, this is just a post of about delegates and my new found appreciation for them. I feel like it's all well and good to know things but to apply them is a different gravy.

The definition:

A delegate is a type that represents references to methods with a particular parameter list and return type - docs.microsoft

Repetitive Repetitive code

Sometimes when we have many functions that have the same input and output, as part of a larger process, can be repetitive and inefficient. It’s also the cause of a lot of lines in the code. A possible solution is to create an interface that abides by the similarities all these methods hold.

For example, methods DrinkingCoke, DrinkingWater and DrinkingLemonade all have a similar “Drinking” process. This would then lead us to create an interface of IDrinking which would contain an interface method “Drink”. This would then be implemented by Beer, Water and Lemonade classes etc blah blah blah 😑.

public interface IDrinking
{
    void Drink();
}

public class DrinkingLemonade : IDrinking
{
    public void Drink()
      => "Drinking lemonade".Dump();
}

//etc.. for Coke and Water ..

public class InTheBeach
{
    public void WhileOnTheBeach(IDrinking drinking)
    {
        drinking.Drink();
    }
}

Enter fullscreen mode Exit fullscreen mode

This, however, may not be the best solution as it could cause a large unraveling of code in interface implementations; something we intended to avoid in the first place when we saw the repetitive code. When viewing code, to me, it is important that there is readability and clarity of intention. I should, therefore, be able to figure out what this person was trying to do by looking at their structure.

Don't get me wrong, I love interfaces as they can enforce structure and help with TDD a lot in my opinion. Believe me when I say this, I have been in the dark implementing interfaces like it's God's gift and thinking there's no other way to do what I want to do in particular cases.

However, the more I learnt, in a weird way, the more I unlearned certain beliefs I held early on. There really are other ways to genericize these processes (I hope I used this word in the right way, and I hope it actually exists lol).

What are signatures

Signatures, to put it simply, is the combination of the return type and the parameter of a method. It's sort of a way to strip a method down to it's core definition, forgetting the beautiful name we give it. What does it take in and what does it spit out?

This is what helps us align functions together. All of the drink methods we mentioned have a void return type and no parameters (they just console log "drinking lemonade" for example). This can be an advantage to us as we expect a method with a certain type of “signature” to be executed at a particular point in a process.

What are delegates

This is where delegates come in, it helps us define an abstract method without the need to create a whole bunch of hoo-ha around it. Let's take a look at the example below of how a delegate summarises the similarities between all three drinking methods.

void DrinkWater() => "Drinking Water".Dump(); 

void DrinkBeer() => "Drinking Beer".Dump(); 

void DrinkLemonade() => "Drinking Lemonade".Dump(); 

delegate void DrinkingSomething(); 

void WhileOnTheBeach(DrinkSomething drinkSomething) 
{ 
  "I'm on the beach..".Dump(); 

  drinkSomething.Invoke(); // or drinkSomething(); same thing 
} 
Enter fullscreen mode Exit fullscreen mode

We have declared the three drinking actions as well as a delegate called DrinkingSomething which looks like it returns void and doesn't have any params. We can see here that the “WhileOnTheBeach” method takes in this delegate and invokes it. What this is really saying is that it expects some sort of void method to invoke, it doesn’t know what it is but it knows it needs to execute at a particular line.

Calling a method that takes in a delegate 📦

Calling this method would look something like this:

WhileOnTheBeach(DrinkWater); 
Enter fullscreen mode Exit fullscreen mode

Notice how the DrinkWater method is used without its parenthesis, this is because we don’t want to call it but rather pass the method as the argument itself (kind of like walking a package through a door and then opening it once you're in the house 📦). When the method gets passed in, it can then be invoked like seen from the previous example inside the WhileOnTheBeach method.

Using delegates completely changed my way of dealing with repetitive code.

Not every repetitive thing needs a damn interface, I wish I knew this sooner 😭. From the example above we can see the clear advantage that delegates have over abstracts in this situation. A lot of truly repetitive code has been avoided and so it becomes more readable.

Funcs?

Now that we understand how signatures work and how we can use them to our advantage, we’re kind of half the way through of understanding Funcs also. At the start you can look at it as a sort of nameless cousin of delegates, or should I say “anonymous” cousin 😉 (you’ll find out why).

It’s because, like delegates, it defines the ins and outs of what a method should have when passed in as an argument. However, the difference is you wouldn’t need to declare it before passing it in, so it’s an anonymous function.

Looking at it more technically, you can say that a Func is a delegate method with generic return types and arguments. This is what it looks like when looking deeper into the definition:

delegate TResult Func<in T, out TResult>(T arg); 
Enter fullscreen mode Exit fullscreen mode

The word Func here is just the name of the delegate, so in reality, Func is a delegate. The one big difference is that they must return something, whereas delegates don't need to.

The WhileOnTheBeach method could instead look like this:

void WhileOnTheBeach(Func<int> doSomethingWithIntParamAndDrink) ...
Enter fullscreen mode Exit fullscreen mode

Actions?

Actions are Funcs (or indeed delegates) but with a void return type. It does exactly what it says in the tin, all remains the same except for the return type, which in the case of Action is always void:

delegate void Action<in T>(T obj); 
Enter fullscreen mode Exit fullscreen mode

It doesn’t necessarily need to have a parameter, neither does a Func (although funcs need to return something).

delegate void Action(); 
Enter fullscreen mode Exit fullscreen mode

Hope you found this post useful, go forth and delegate!

Discussion (0)