BLOG
27 December 2022

Advanced C#: Delegates

tech

C# is a fantastic programming language. It has evolved over the years to meet the highest standards and, in many cases, to set them. At the moment of writing this article, the current version is C# 10. Over that many versions, a fair share of features was added to the language; that is exactly what this new blog series will be about. Don't worry; it won't be yet another list on "top x new features in version y". Instead, we will talk about language features that are more advanced and often overlooked by developers.   

The first feature we are going to look at is the delegates. The delegates work almost the same as the function pointer you may have used in the other languages. However, using function pointers allows us to leverage two significant benefits. The first is to set the pointer function(s) in runtime. Second, leverage the callback approach when the function from the pointer is complete.   

If you want to download the code, it can be found here.

What is it? 

First, we need to understand what the delegates are. The best way to think about it is to treat delegate as a required method signature. This means we are sure what the function input parameters and output will be. Thanks to the use of delegates, we can reference and invoke a method without necessarily making this method a part of the scope or class you are working with.   

There are three prominent cases when you should consider using delegates. First is when your solution focuses on extensibility. Using delegates, in this case, lets you inject additional methods that are to be executed. Secondly, when working with events. This is most relevant to the situations where we work with a user interface. In these cases, that event triggered by the UI actions depends heavily on the delegates. The third situation is when we want to rely on callbacks. 

But enough theory – it's time for some coding now. The code we will be working on can be found on GitHub. 

Coding time 

As always, we will be discussing the topic by coding it. And, as always, the problem’s domain doesn’t matter much. In this case, we will create a simple console application to work with some change systems. As I said, this will be just a background for the delegates discussion.   

If we want to work with the changes, we need something to represent the change. In our case, we will keep the change data in the super simple class ChangeDetails.cs. The class has three fields: Id, Name and Valid. First two are self-explanatory. The valid field tells us if the change is valid or not. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 namespace Delegates; public class ChangeDetails { public int Id { get; set; } public string Name { get; set; } public bool Valid { get; set; } }

 

The second file I want you to look at is the ChangeUtils.cs. In this file, we keep all of our utility functions that can operate on change in our “system”.   

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 namespace Delegates; public class ChangeUtils { public void SaveChangeToList() { Console.WriteLine("Change saved to list"); } public void PrioritizeChange() { Console.WriteLine("Change Prioritized"); } public void DoPaperWork(ChangeDetails changeDetails) { Console.WriteLine("Do paper work for {0}", changeDetails.Name); } public bool Process(ChangeDetails changeDetails) { return changeDetails.Valid; } }

They represent the typical activities we can encounter while processing changes. Plus, they are primarily wary when it comes to the method signature. The first two take no arguments and serve only the log function, informing us that change was saved or prioritised. The third method also logs info, but the signature is different. Now we do have the parameter. So, the signature varies from the previous two. Finally, we have the Process method. In this case, we have both input and output.   

Now that we have both the POCO objects and utils in place, it is time to put delegates to work. Please shift your attention to the file MakeChanges.cs, as this is the class that has the code, we want to discuss in this blog post.   

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 namespace Delegates; public class MakeChanges { public delegate void ChangeValidated(); public delegate bool Processing(ChangeDetails changeDetails); public delegate void ProcessCompleted(ChangeDetails changeDetails); public ChangeValidated OnChangeValidated { get; set; } public Processing OnProcessing { get; set; } private bool Validate(ChangeDetails changeDetails) { try { ArgumentNullException.ThrowIfNull(changeDetails); } catch (Exception e) { return false; } OnChangeValidated?.Invoke(); return true; } public void Process(ChangeDetails changeDetails, ProcessCompleted? processCompleted = default) { if (!Validate(changeDetails)) return; if (OnProcessing?.Invoke(changeDetails) == true) { processCompleted?.Invoke(changeDetails); } } }

This class has four distinct parts, and we will discuss them one-by-one. First, there are three delegate definitions.   

1 2 3 public delegate void ChangeValidated(); public delegate bool Processing(ChangeDetails changeDetails); public delegate void ProcessCompleted(ChangeDetails changeDetails);

As we can see, the "delegate" keyword must be used to create a delegate. Apart from that, it appears to be a standard method definition. It has a name, a return type, and a list of parameters, and thanks to that definition, we know what kinds of methods can be used with that delegate - those with the same signature. 

The second is in charge of the properties based on delegates. As a result, you can add methods to those delegates from anywhere you'll be using that class. This is extremely useful because the methods can be associated during runtime and also set based on application actions. 

1 2 3 4 5 6 7 8 9 10 11 public ChangeValidated OnChangeValidated { get; set; } public Processing OnProcessing { get; set; }

The private method comes next. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 private bool Validate(ChangeDetails changeDetails) { try { ArgumentNullException.ThrowIfNull(changeDetails); } catch (Exception e) { return false; } OnChangeValidated?.Invoke(); return true; }

Finally, we can see delegates in action in this method. The method itself is extremely simple. If the change details object is null, we want to throw an error and return false in catch. If the object is not null, we pass it through validation his is the moment when delegate comes into play. We want to execute (Invoke) the methods associated with OnChangeValidated property. This is the awesome part. The class holding the delegate has no idea what those associated methods are. And, it doesn’t care. All it cares about is for the methods to have matching signature.  

The fourth and final section is the public method. 

1 2 3 4 5 6 7 8 public void Process(ChangeDetails changeDetails, ProcessCompleted? processCompleted = default) { if (!Validate(changeDetails)) return; if (OnProcessing?.Invoke(changeDetails) == true) { processCompleted?.Invoke(changeDetails); } }

We can see two more use cases for delegates in this method. First there is OnProcessing?.Invoke(changeDetails) == true. In this case we again call all the methods associated with delegate bases property. If all the invocations where successful, the Invoke method will return true. The second usage is the callback method that got passes as a delegate based type parameter. 

Putting it all together 

Now that all of the pieces are in place, it's time to look at how our class is being used. The executable code is stored in the Program.cs file. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using Delegates; Example1(); Console.ReadKey(); void Example1() { var change = new ChangeDetails() { Id = 1, Name = "Name 1", Valid = true }; var changer = new MakeChanges(); var changeUtils = new ChangeUtils(); changer.OnChangeValidated += changeUtils.SaveChangeToList; changer.OnChangeValidated += changeUtils.PrioritizeChange; changer.OnProcessing += changeUtils.Process; changer.Process(change, changeUtils.DoPaperWork); }

The main part of the file is the Example1 method. The last four lines are the ones that should draw our attention.  The changer.OnChangeValidated += changeUtils.SaveChangeToList and changer.OnChangeValidated += changeUtils.PrioritizeChange lines add a method to the delegate based property. Please notice the fact the we are assigning the method itself and not the result of the method call - that is why there are no parentheses. Next there is changer.OnProcessing += changeUtils.Process line. It does the same kind of code, simply for the different delegate. Last, but not least, there is changer.Process(change, changeUtils.DoPaperWork line. We call the Process method and in the call we pass callback function. Again as a parameter of the delegate type. Now that everything is in place, we can simply press the run button and see what happens. 

The results are exactly as we have expected. Call the method passed to the delegates got called. At this point I got a suggestion. Please run this code again the on debug mode and go step by step. This will give you an excellent insight into what is going on and in what order the pars of the code are being executed.  
 

Summary  

This was the first article in a series on advanced C#. We discussed delegates, how they function, and how they can be used. I hope you enjoyed reading that and found the article useful. 

If you have any questions, please drop me a line at karol.rogowski@softwarehut.com. 

Till next time. Keep coding. 

 

 



Author
Karol Rogowski
Head Of Engineering

Working in IT since 2009. Currently working as Head of Engineering at SoftwareHut and as an academic teacher at Białystok Technical University. Co-founder of meet.js Białystok. Book and articles author. Father, husband, huge H.P. Lovecraft fan and terrible poker player.