Click here to Skip to main content
Click here to Skip to main content

Understanding Delegates in C# for Beginners - Basics

, 23 Aug 2013
Rate this:
Please Sign up or sign in to vote.
The story of de-coupling.

Introduction

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. Delegates are used in many design patterns. Publisher-Subscriber is a very common and well known example of a design pattern where delegates are used.

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.

The meaning of the word delegate is:    

noun 
  1. a person designated to act for or represent another or others; deputy; representative, as in a political convention. 
  2. (formerly) the representative of a Territory in the U.S. House of Representatives.
  3. a member of the lower house of the state legislature of Maryland, Virginia, or West Virginia.
verb (used with object) 
  1. to send or appoint (a person) as deputy or representative. 
  2. 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.  

Prerequisites

Please read the following articles before proceeding with this article:

  1. MSDN - What are Delegates
  2. MSDN - Using Delegates 
Reading the above-mentioned articles is NOT optional.

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.

Using the code 

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.

Let us get going

Let us assume that there is a software development firm named IWorkForThee Corporation. This company specializes in developing state-of-the-art software libraries aimed at solving computation problems. Let us also assume that IWorkForThee Corporation produces closed source components. IWorkForThee Corporation sells 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:

namespace DoSomethingLibrary
{
    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:

  1. Add DoSomethingLibrary DLL as a reference to their executable's project in Visual Studio
  2. Use the DoSomethingLibrary namespace to access the library programmatically
  3. 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 weeks 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:

  1. XML formats of INeedThee Corporation and GreedyNeedy Corporation are substantially different and would require huge effort.
  2. Release date would surely be missed and other consumers may not be very happy
  3. 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. 
  4. Costs would go up and the profits would go down.
  5. 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:

  1. 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!
  2. 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. 

namespace DoSomethingLibrary
{
    /*
     *This 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 delegate void Logger(string firstParam, string secondParam);

    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 executables code is also added below:

using System;
/*
 * 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;

namespace UnderstandingDelegates
{
    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:

  1. ClientsCustomizedLoggerOne representing an HTML logger
  2. 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 System;
using DoSomethingLibrary;

namespace UnderstandingDelegates
{
    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 MulticastDelegate?

  • MulticastDelegate is fancy way of saying that you can hook multiple methods to the delegate.
  • SinglecastDelegate is a great way of saying that the delegate has just one method hooked to it. There is actually no such thing as SinglecastDelegate. All delegates are technically MulticastDelegate 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 System;
using DoSomethingLibrary;

namespace UnderstandingDelegates
{
    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 MulticastDelegate  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.

The End

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.   

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

_msiyer_
Software Developer HCL Technologies
India India
I live in Chennai (erstwhile Madras), India.
I am an animal lover and love a few humans too.
 
I graduated in Mechanical Engineering but fell in love with Software Engineering. I am mad about FOSS. I work on Windows at my workplace but Linux and FreeBSD rule my home.
 
Mathematics, English literature and Hindi literature save me from boredom. Always.
Follow on   Twitter

Comments and Discussions

 
QuestionVery well done PinmemberMember 107314377-Apr-14 10:29 
AnswerRe: Very well done Pinprofessional_msiyer_14-Apr-14 9:34 
AnswerAwesome Pinmemberbpc19895-Mar-14 22:50 
GeneralRe: Awesome PinprofessionalIDisposable6-Mar-14 3:34 
QuestionExcellent Explanation PinmemberMember 1035865724-Oct-13 15:54 
Questionmy vote of 0 PinmemberFatCatProgrammer22-Aug-13 4:35 
AnswerRe: my vote of 0 PinmemberIDisposable22-Aug-13 5:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 23 Aug 2013
Article Copyright 2013 by _msiyer_
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid