Understanding Delegates in C# for beginners
Roadblocks to proper understanding
Delegates as a technical concept in C# create a lot of confusion in the beginners’ mind. It is a fairly simple concept but most of the examples floating around on the web are so trivial that they do not expose the real benefits of delegates. Look at the below sample:
public delegate double Delegate(int a,int b);
class Class1
{
static double fn_ToHookToTheDelegate(int val1, int val2)
{
return val1 * val2;
}
static void Main(string[] args)
{
//Creating the Delegate Instance
Delegate delObj = new Delegate(fn_ToHookToTheDelegate);
Console.Write("Please Enter Values");
int v1 = Int32.Parse(Console.ReadLine());
int v2 = Int32.Parse(Console.ReadLine());
//Call delegate for processing
double res = delObj(v1, v2);
Console.WriteLine ("Result: " + res);
Console.ReadLine();
}
}
In this example, everything exists within the confines of the same class. The delegate (or the hook, as I call it) and the function to be hooked to it are both in the same class. The following line of code in the Main() makes no pragmatic sense:
Delegate delObj = new Delegate(fn_ToHookToTheDelegate);
We could have saved ourselves some time and typing by simply calling the function instead of wrapping it in a delegate. When the uninitiated reader is presented with such code, there is absolutely no learning that takes place. This is exactly why I decided to create a series of elaborate articles to note down my thoughts on topics I learn. The sample above and the discussion may not make any sense to the reader at present, but please keep reading. Things will surely become crystal clear by the end of this article, I promise 😉
Jumping over the roadblocks
I frequented Stackoverflow and tried to find my “eureka” moment there. I received a lot of help, no doubts, but I was still far from real understanding. That is when I started spending inordinate amounts of time conjuring up hypothetical scenarios where I would use Delegate. This exercise helped me a lot. I started reading Jon Skeet’s C# In Depth and Mark Michaelis’s Essential C# and got a better understading of the concept. I am, by no means, an expert but this is what I feel a newcomer should do:
- Understand that programming is not for the weak hearted. Lack of talent is not an issue, lack of determination is.
- Join Stackoverflow
- Read books. There is no alternative.
- Try to relate everything to the world around us. That is the real meaning of Object Orientation – a programming model that tries to mimic the real world, the objects in it and their interactions.
The meaning of the word delegate is:
noun
- a person designated to act for or represent another or others; deputy; representative, as in a political convention.
- (formerly) the representative of a Territory in the U.S. House of Representatives.
- a member of the lower house of the state legislature of Maryland, Virginia, or West Virginia.
verb (used with object)
- to send or appoint (a person) as deputy or representative.
- to commit (powers, functions, etc.) to another as agent or deputy.
It is utterly important to understand the meaning of a technical term. This helps in gaining insights that would be otherwise impossible to gain.
Background
Basic knowledge of C# is assumed. This means the reader should possess a fairly basic understanding of Object Oriented Programming and is at ease with simple C# programs and Visual Studio. I will not be explaining what a delegate is. I will explain one scenario where it can be employed. Delegates are very powerful and can be gainfully employed in a lot of design patterns. Please read the following articles before proceeding with this article:
Reading the above-mentioned articles is NOT optional. I have tried to explain the concept using a small story that tries to be very close to the real world. The code sample added in this article is also trivial but gives the reader a feel of how to use delegates in the real world for modular software design. Although the code sample is in C#, the concept is universal. I have intentionally avoided topics that could complicate things for beginners. I would like the reader to get a basic understanding of the concept and build a mental picture before dealing with technicalities. I would take the reader for a deep dive once the story ends. Read the code provided thoroughly. The code is heavily commented to clear any doubts the reader might have. Reading the code comments is NOT optional. Read the comments thoroughly. The code compiles just fine.
A moving story
The problem
There is a software development firm, IWorkForThee Corporation. This company specializes in developing state-of-the-art software libraries aimed at solving computation problems. IWorkForThee Corporation produces closed source components.They sell a lot of software. This means they have a great list of customers. Now, IWorkForThee Corporation has sold the first version of its new library DoSomethingLibrary. This library is in the form of a DLL. The code of the library looks like this:
public class HeavyWeightWorker
{
public void HeavyWeightWork()
{
/*HeavyWeightWork code is an emulator which I am using to emulate
*some huge processing or some huge job.
*Let us imagine that this is a library
*that does some heavy data crunching OR some
*extremely complex data access job etc..
*/
/*Let us imagine Console.WriteLine() as a function that does some heavy work
*/
Console.WriteLine("Heavy Weight WORK Step 1");
/*After each step this library tries to LOG.*/
LogMessage("Heavy Weight WORK Log", "Step 1");
Console.WriteLine("Heavy Weight WORK Step 2");
/*After each step this library tries to LOG.*/
LogMessage("Heavy Weight WORK Log ", "Step 2");
Console.WriteLine("Heavy Weight WORK Step 3");
/*After each step this library tries to LOG.*/
LogMessage("Heavy Weight WORK Log ", "Step 3");
}
private static void LogMessage(string firstParam, string secondParam)
{
/*
*This logger has ‘=’ used as a decorator
*In real scenarios the logger may be very complex.
*Let us assume this is an HTML logger
*/
Console.WriteLine("=============================");
Console.WriteLine(firstParam + " – " + secondParam);
Console.WriteLine("=============================");
}
}
This is an absolutely simple way of doing things and it simply works. The consumers do the following to consume the library:
- Add DoSomethingLibrary DLL as a reference to their executable’s project in Visual Studio
- Use the DoSomethingLibrary namespace to access the library programmatically Create an instance HeavyWeightWorker and off they go…
The consumers are happy because they can use the library without much fuss. The IWorkForThee Corporation is also reaping the benefits of happy consumers. One of IWorkForThee Corporation’s consumers, the INeedThee Corporation has been in business with them for the past few years. They demand a new version of the DoSomethingLibrary with some advanced features. This is exactly what IWorkForThee Corporation was working on. So they tell INeedThee Corporation that they can have the new version in a few days. But INeedThee Corporation had another demand that threw IWorkForThee Corporation’s plans off balance. A new logging mechanism was demanded. They needed an XML log with a pre-defined format. This XML would then be used by INeedThee Corporation’s logging utility to perform advanced analysis etc.. IWorkForThee Corporation’s lead developer says that the new logging function can be completed in a week’s time. This made life easy for the management. But there was another bomb about to explode. Another customer, GreedyNeedy Corporation was very happy with the original logging mechanism. But they wanted more. They wanted another logger to log in XML format. The only issue was that their XML format was completely different from INeedThee Corporation’s format. The management was completely clueless on what approach to follow. They could implement all the loggers needed by all the consumers. But the following issues were noted down in the emergency meeting:
- XML formats of INeedThee Corporation and GreedyNeedy Corporation are substantially different and would require huge effort.
- Release date would surely be missed and other consumers may not be very happy
- With so many loggers available there has to be a mechanism for the consumer to choose. This would require a change in the HeavyWeightWorker Class’s structure.
- Costs would go up and the profits would go down.
- What happens if the consumers change the format again?
There was a guy in the meeting who usually says very little. He spends time learning new things. He learns design patterns etc.. He is so good at making things beautiful that the whole idea of implementing all the loggers in the library made him sick. He proposed a solution that could change everything.
The solution
The solution is to remove all loggers from the library. Why? If consumers are not happy with the logging that the library provides and need custom logging, let them implement the logging. In any case, the custom logging mechanism can be best implemented and maintained by the consumers. This is fairly simple. Right? No. This is not simple. With a little thought one can see that the logger needs to be detailed and for that reason it needs to be cleanly integrated with the DoSomethingLibrary DLL’s logic. If logging functionality resides in an outside entity, how do we integrate it with DoSomethingLibrary DLL’s logic so that we have a log after each step of the job? This is easily achieved if all logging functionality is included in the DLL itself. But that was the problem we wanted to solve. So what options do we have? Delegates come to our rescue. Using delegates we can delegate (look at the meaning) the logging functionality to the consumers. This will help us in the separation of concerns. That is, the DoSomethingLibrary is extremely good at doing what it does. Custom logging was getting too heavy for the library. This overhead of maintaining the custom loggers was just too much of unnecessary work. The guy who proposed the idea was asked to provide proof of concept models. He did that and managed to convince the management of the efficacy of the idea. The consumers and specifically the INeedThee Corporation and GreedyNeedy Corporation were informed of the decision to delegate DoSomethingLibrary’s logging functionality to them. This seemed to be a great option for all. Why? Because:
- The logging is completely out of DoSomethingLibrary and IWorkForThee Corporation is happy as this means less code to maintain and no dependency on the consumers at all!
- The logging is completely in control of consumers so they are happy. Now they can do whatever they want.
The version 2 of the DoSomethingLibrary is added. The code is self explanatory and the comments are elaborate:
public delegate void Logger(string firstParam, string secondParam);
/*
*The above line is a public delegate declared at the base namespace level for global presence.
*The question is WHY do we need to have a DELEGATE here?
*The answer is: I do not want to implement the LOGGING logic. Why? Well, my consumers are many
*and all are equally demanding. They all need different types of logging. Some need HTML logging,
*some need XML logging for their custom log analyzer, some need plain text logging etc…
*This is hell for me. How am I going to support all their demands. I cannot. Thus, I ask them to
*implement LOGGING on their side. I am providing an INTERFACE(literal sense) in the guise of a DELEGATE.
*A DELEGATE is a HOOK.
*This is the hook that is needed for consumers to hook their custom loggers into the library.
*/
public class HeavyWeightWorker
{
public Logger ConsumerLoggerHook;
public void HeavyWeightWork()
{
/*After each step this library tries to LOG. But NOTE that this library
*has no LOGGER implemented. Instead, this library has judiciously DELEGATED
*the logging responsibilty to the CONSUMER of this library.
*/
Console.WriteLine("Heavy Weight WORK Step 1");
/*After each step this library tries to LOG.*/
ConsumerLoggerHook("Heavy Weight WORK Log ", "Step 1");
Console.WriteLine("Heavy Weight WORK Step 2");
/*After each step this library tries to LOG.*/
ConsumerLoggerHook("Heavy Weight WORK Log ", "Step 2");
Console.WriteLine("Heavy Weight WORK Step 3");
/*After each step this library tries to LOG.*/
ConsumerLoggerHook("Heavy Weight WORK Log ", "Step 3");
}
}
The version 1 of the DoSomethingLibrary DLL was so simple that the consumer code needed to integrate with it was trivial. That is not the case now. Thus, the consumer executable’s code is also added below: Let us assume that I have purchased the DoSomethingLibrary DLL from a vendor. I have to add the DLL as a reference to my executable’s project in Visual Studio. I also have to use the DoSomethingLibrary namespace to access the Logic in the DLL:
using DoSomethingLibrary;
class Program
{
static void Main(string[] args)
{
/*
* Creating an object of the lone class PrintingManiac in the DoSomethingLibrary
*/
HeavyWeightWorker worker = new HeavyWeightWorker();
/*
* HOOKING my custom logger to the DoSomethingLibrary DLL.
* I get the best of both the worlds. I have a well-tested and efficient library working for me
* AND I have the best logging avaliable.
* The DoSomethingLibrary DLL has no knowledge of what logging this executable is going to use.
* This executable has to just satisfy the requirements
* of the DELEGATE signature of DoSomethingLibrary DLL.
*/
worker.ConsumerLoggerHook += new Logger(ClientsCustomizedLoggerTwo);
worker.HeavyWeightWork();
Console.ReadLine();
}
public static void ClientsCustomizedLoggerOne(string firstParam, string secondParam)
{
/*
*This logger has ‘=’ used as a decorator
*In real scenarios the logger may be very complex.
*Let us assume this is an HTML logger
*/
Console.WriteLine("=============================");
Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " – " + secondParam);
Console.WriteLine("=============================");
}
public static void ClientsCustomizedLoggerTwo(string firstParam, string secondParam)
{
/*
*This logger has ‘-‘ used as a decorator
*Let us assume this is an XML logger
*/
Console.WriteLine("——————————");
Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " – " + secondParam);
Console.WriteLine("——————————");
}
}
Going deeper
What we have seen above is how a delegate is used. We have not dealt with anything even remotely advanced. Let us now dive a bit deeper. In the sample code above we could see that the consumer has two logger functions available:
- ClientsCustomizedLoggerOne representing an HTML logger
- ClientsCustomizedLoggerTwo representing an XML logger
This particular customer hooked the ClientsCustomizedLoggerTwo with the DoSomethingLibrary. So, the customer still has ClientsCustomizedLoggerOne sitting idle doing nothing. But the customer has the option of using either of the loggers as per requirements. Thus the customer can do the following and hook the ClientsCustomizedLoggerOne to the DoSomethingLibrary:
using DoSomethingLibrary;
class Program
{
static void Main(string[] args)
{
HeavyWeightWorker worker = new HeavyWeightWorker();
/*Following is the only line that needs to change so as to HOOK a different
* logger to the DoSomethingLibrary. In the earlier example ClientsCustomizedLoggerTwo
* was HOOKed and it is ClientsCustomizedLoggerOne now that has been hooked.
*/
worker.ConsumerLoggerHook += new Logger(ClientsCustomizedLoggerOne);
worker.HeavyWeightWork();
Console.ReadLine();
}
public static void ClientsCustomizedLoggerOne(string firstParam, string secondParam)
{
/*
*This logger has ‘=’ used as a decorator
*In real scenarios the logger may be very complex.
*Let us assume this is an HTML logger
*/
Console.WriteLine("=============================");
Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " – " + secondParam);
Console.WriteLine("=============================");
}
public static void ClientsCustomizedLoggerTwo(string firstParam, string secondParam)
{
/*
*This logger has ‘-‘ used as a decorator
*Let us assume this is an XML logger
*/
Console.WriteLine("——————————");
Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " – " + secondParam);
Console.WriteLine("——————————");
}
}
This is the new executable and it can log using the ClientsCustomizedLoggerOne logger that represents an XML logger. What happens if the consumer wants to hook both loggers to the DoSomethingLibrary? That is, how can the consumer hook both the HTML and XML loggers to the DoSomethingLibrary? This is where the MulticastDelegate concept comes into picture. All delegates must be convertible to System.Delegate. All delegate types are derived from System.MulticastDelegate. System.MulticastDelegate derives from System.Delegate. Thus all delegates are basically inherently Multicast. When we hook one method to the delegate it behaves as a Singlecast delegate. So what exactly is Singlecast and Multicast Delegate?
- Multicast Delegate is fancy way of saying that you can hook multiple methods to the delegate.
- Singlecast Delegate is a great way of saying that the delegate has just one method hooked to it. There is actually no such thing as Singlecast Delegate. All delegates are technically Multicast Delegate types. When only one method is hooked to the delegate people tend to call it SinglecastDelegate.
If the consumer wants to have both logs(HTML and XML) available for whatever reasons, the MulticastDelegate helps the consumer to do so. The below code will show what needs to be done so as to achieve the goal of hooking both loggers to the DoSomethingLibrary:
using DoSomethingLibrary;
class Program
{
static void Main(string[] args)
{
HeavyWeightWorker worker = new HeavyWeightWorker
/*The following two lines will HOOK the two loggers to the DoSomethingLibrary.
* There is no more magic needed. That is it!!!
*/
worker.ConsumerLoggerHook += new Logger(ClientsCustomizedLoggerOne);
worker.ConsumerLoggerHook += new Logger(ClientsCustomizedLoggerTwo);
worker.HeavyWeightWork();
Console.ReadLine();
}
public static void ClientsCustomizedLoggerOne(string firstParam, string secondParam)
{
/*
*This logger has ‘=’ used as a decorator
*In real scenarios the logger may be very complex.
*Let us assume this is an HTML logger
*/
Console.WriteLine("=============================");
Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " – " + secondParam);
Console.WriteLine("=============================");
}
public static void ClientsCustomizedLoggerTwo(string firstParam, string secondParam)
{
/*
*This logger has ‘-‘ used as a decorator
*Let us assume this is an XML logger
*/
Console.WriteLine("——————————");
Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " – " + secondParam);
Console.WriteLine("——————————");
}
}
This is simply beautiful. So how does the delegate manage multiple methods hooked to it? Always remember that any delegate is a MulticastDelegate. Thus one can easily hook many methods to a delegate. Each delegate maintains a list of hooked methods. This list is known as the Invocation List. The += operator helps in adding more hooks to the list. It is also possible to remove a hook by using the -= operator. When a Multicast Delegate is invoked, all the methods in the invocation list are invoked sequentially in the order in which those methods were added to the list. If an exception occurs during the processing of any of the methods in the list, the other methods in the list are not invoked. This limitation can be overcome if one can gain complete access to the invocation list. The GetInvocationList function helps in this regards. There are great number of things that can be achieved using delegates. Many design patterns depend on them. Delegates are a great concept. The details in this article should be enough to get a beginner to appreciate the beauty of this concept.
Conclusion
The story ends here folks. A small innovative step using a feature provided by the language helped the vendor and the consumers to de-couple. I hope everyone enjoyed the story. Please read the code as many times as possible or needed. The comments are elaborate.