Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Delegates in C# - Attempt to Look Inside - Part 1

0.00/5 (No votes)
6 Oct 2010 1  
How to understand and use delegates in your program

Introduction

Many programmers try to avoid delegates in their development. We all know that we can develop anything we want with our familiar development tools and methods and that a particular functionality can be developed in many different ways, so why bother and use delegates when we can be better off without them.

I think this is just an excuse - an attempt to mask the fact that many developers (even advanced ones) sometimes just don't quite understand the notion of delegates.

Challenge

There are tons of articles, tutorials, and blogs explaining the delegates. It is very challenging to make another attempt to try to explain something that was explained that many times already.
Still, I decided to write several articles trying to lunge deeper into the essence of a delegate and hoping that maybe this will help a programmer to better understand it and use it in his/her development for the benefit of an application. This is the first article that will introduce to you a delegate - why it was created, what it is, what is behind it, and how you can use it.

The Essence of Delegate

Let’s start from the semantics. Google English Dictionary explains the meaning of delegate:
Delegate: a person sent or authorized to represent others, in particular an elected. Representative sent to a conference.

I want to single out "sent or authorized to represent others".
So a delegate in C# programming is an entity authorized to represent others and it can be sent for this purpose. This means that we have something that needs to be represented.
What is it? Well in our case, it is a Method (Function). The method resides somewhere. It may belong to an object or it maybe just a static method. If the method needs to have a remote representation, it uses a delegate. The delegate can be sent someplace far from the represented method but if need be, the delegate can easily invoke the method no matter how far apart they are. So why was the entity called “delegate” introduced? It was mainly done to be able to invoke any method remotely. It also allows us to somehow pass the method as a parameter.

We are never surprised that a regular object can be sent as a parameter to a function, but what about a method? Can a method/function be sent as a parameter?
Not directly, no.
But (and this is a power of a delegate) its authorized representative (delegate) can be sent instead.
Ok, now its time to get down to business. Up to now, we know that a delegate represents a method (function). But let us analyze how to use a delegate step by step.

Step 1: Declare a Delegate

public delegate void RemoteOperation();

Microsoft approached the C# delegates creation in a very object oriented way. Unlike C++, where a similar functionality was done through the function pointer, in our case when a compiler meets the above code, it creates a class.
Don’t be surprised. When you declare a delegate, a class being created behind the scene by a compiler even though you cannot see it explicitly. The created class name will be RemoteOperation. Based on the compiler version, it inherits either from MulticastDelegate class(all latest versions) or from Delegate class. MulticastDelegate class inherits from Delegate class. It has constructors, which means you will have to instantiate it with the new() keyword. The base class Delegate and MulticastDelegate class has some inner relationships. If you are a devoted object oriented programmer, you must have heard about the programming patterns that were first introduced by the Gang of Four (GOF). The MulticastDelegate -> Delegate is a perfect example of the Structural Composite Pattern. This is not our current topic, but the notion is very simple: Delegate class presents component. MulticastDelegate class presents composite. MulticastDelegate (as a composite class) internally implements a private array of Delegate class objects. You can add or remove objects of MulticastDelegate (Delegate) from this array at your will. The Base Delegate class has a set of properties and methods. It has a method called Invoke () which has a delegate signature. This signature is taken from your declare statement. In our example, the Invoke method does not take any parameters and returns void. It has two extremely valuable properties:

  1. Target – to keep reference of the object where the Method is implemented
  2. Method – to keep MethodInfo object for this object

We will go into these properties later.


Note:

If the declaration of the delegate is different (see below):

public delegate int DifferentSignatureDelegate (int id , string name ); 

Invoke method returns integer and receives integer and string as parameters.
In other words, the Invoke method mimics your delegate declaration.


Now let us prepare a method that we want in the future to be represented by a delegate.
It will be a simple static function DoNothing () which accepts no parameters and returns void:

public static void DoNothing()
{
Console.WriteLine( "Nothing in nothing out, no action taken");
}

Step 2: Instantiate the Delegate

RemoteOperation operationVoid = new RemoteOperation(DoNothing); 

Compiler creates an instance or the RemoteOperation class.
As you can see, we use a new keyword and a parameter to this constructor that is the function itself, not a string.
Remember Invoke method?
Before instantiation, the compiler verifies if the signature of DoNothing is the same as the signature of Invoke method of RemoteOperation class.
In case of verification failure, the compiler returns an error: No overload for "somename" matches delegate "delegatename". If verification succeeded, the object delegate of RemoteOperation type is created. Then compiler creates a new instance of Delegate class. It sets Target property of the instance to null because delegates represent a static function.
If instead of the static function, we provided the new delegate with an object instance function, then the property Target would have a reference to the object containing the function. Method property will be set to Void DoNothing (). After that, it adds the instance of Delegate into internal Array of Delegates of RemoteOperation object.

Delegates1.gif

This picture is not a proper UML diagram, it just shows what happens internally when we instantiated the delegate.

Newly created objects have all the information necessary to represent the actual function DoNothing ():

  1. It has this function signature.
  2. It knows where this function resides.
  3. It can call the function.

And the good news: we can use this object as a parameter to a function.

Step 3: Execute the Delegate

To execute the delegate (invoke the function it represents) is very simple:

operationVoid(); 

This statement is the same as:

operationVoid.Invoke();

At this moment, the program finds the function no matter where it resides and calls it.
Can a delegate have multiple representation? The answer is obvious: yes, it can.
A delegate object can represent as many methods(functions) as you want. One restriction is that all those functions must have the same signature. As you already know, it is done through the inner array of Delegate class objects. You can view this array during debug and get it via method GetInvocationList (). Let us create a class with a method:

public class SomeClass
{
public void AnotherFunction (){}
}

Then we instantiate the class:

SomeClass o1 = new SomeClass (); 

And add the reference to the class method into our delegate:

operationVoid += new RemoteOperation(o1.AnotherFunction); 

Picture 2 displays what is internally in the operationVoid object.

Delegates2.gif

When you invoke – all functions in the delegate will be invoked(executed) in the order of placement. You can also remove a function out of the invocation list by:

operationVoid -= new RemoteOperation(o1.AnotherFunction); 

This will remove the Delegate object corresponding to the function from the array of Delegates.

Example

Enough theory, now let’s do some coding. The idea behind this example is using a remote control to turn a TV set on or off. I used a simple C# console application. In the main namespace of the application, first I declare a public delegate:

public delegate void RemoteOperation(); 

Television class has a simple constructor that sets the TV model and has 2 methods TurnOn and TurnOff with simple messages inside.

public delegate void RemoteOperation();
	
/// <summary>
/// Presents a TV of a particular model
/// </summary>
public class Television	
{
    private string model;
			
    /// <summary>
    /// Returns TV model
    /// </summary>
    public string Model { get { return model; } }
		
    /// <summary>
    /// instantiates TV
    /// </summary>
    /// <param name="model"></param>
    public Television( string model)
    {	
        this.model = model;	
    }
			
    /// <summary>
    /// Turns TV on
    /// </summary>
		
    public void TurnOn()		
    {
        Console.WriteLine( this.model + " is on");
    }
			
    /// <summary>
    /// Turns TV off
    /// </summary>
    public void TurnOff()
    {
        Console.WriteLine( this.model + " is off");
    }
}	 

TV owner class has 2 readonly properties TV and Name.

/// <summary>
/// Presents TV owner
/// </summary>
public class TvOwner
{
    private Television myTV;
    private string name;
			
    /// <summary>
    /// Returns owner name
    /// </summary>
    public string Name { get { return name; } }
			
    /// <summary>
    /// Returns owner's TV
    /// </summary>
    public Television TV { get { return myTV; } }
			
    /// <summary>
    /// Instantiates owner
    /// </summary>
    /// <param name="name"></param>
    /// <param name="tvModel"></param>
    public TvOwner( string name, string tvModel)
    {
        this.name = name;
        myTV = new Television(tvModel);
    }
}	

Friend class has a private variable of RemoteOperation type that is a delegate type.
It also has two methods:

  1. SetRemoteOperation
  2. RemoveRemoteOperation

Those methods receive one of the parameters of RemoteOperation type and set the private variable remote by adding reference functions into it. The owner of the TV can directly call TV methods TurnOn and TurnOff but he/she can also call it through a remote control. Moreover, he/she can pass the remote control operations to a friend and now the friend can run the TV. This is exactly what a delegate is used for.

/// <summary>
/// Presents a person who can use remote control
/// </summary>
public class Friend
{
    private RemoteOperation remote;
    private string name;
			
    /// <summary>
    /// Instantiates the friend
    /// </summary>
    /// <param name="name"></param>
    public Friend( string name)
    {
        this.name = name;
    }
			
    /// <summary>
    /// Sets remote operation for a particular device
    /// </summary>
    /// <param name="remote"></param>
    /// <param name="deviceName"></param>
    public void SetRemoteOperation( RemoteOperation remote, string deviceName)
    {
        this.remote += remote;
        Console.WriteLine( "RemoteOperation for " + deviceName + " set");
    }
			
    /// <summary>
    /// Removes remote operation for a particular device
    /// </summary>
    /// <param name="remote"></param>
    /// <param name="deviceName"></param>
    public void RemoveRemoteOperation( RemoteOperation remote, string deviceName)
    {
        this.remote -= remote;
        Console.WriteLine( "RemoteOperation " + deviceName + " removed");
    }
			
    /// <summary>
    /// Executes remote operation
    /// </summary>
    public void UserRemote()
    {
        if ( null != remote)
        {
             Console.WriteLine(name + " clicked remote '" + remote.GetType().FullName
             + " " + remote.Method.Name + "'");
             remote();
        }
    }    
}	 

Program to run:

/// <summary>
/// Execution program class
/// </summary>
public class Program
{
    /// <summary>
    /// Main procedure
    /// </summary>
    /// <param name="args"></param>
    static void Main( string[] args)
    {
	//create tv owner:
	TvOwner owner = new TvOwner( "Ed", "Samsung");
				
	//create tv owner:
	TvOwner nowner = new TvOwner( "Nick", "Sony");
				
	//create friend:
	Friend friend = new Friend( "Alex");
					
	//instantiate delegate for turning tv on:
	RemoteOperation setTvOn = new RemoteOperation(owner.TV.TurnOn);
					
	//instantiate delegate for turning tv on:
	RemoteOperation aaa = new RemoteOperation(nowner.TV.TurnOn);
					
	//instantiate delegate for turnting tv off:
	RemoteOperation setTvOff = new RemoteOperation(owner.TV.TurnOff);
					
	//pass function turnOnTv to the friend:
	friend.SetRemoteOperation(setTvOn, owner.TV.Model);
					
	//pass function turnOnTv to the friend:
	friend.SetRemoteOperation(aaa, nowner.TV.Model);
					
	//friend is using the remote to turn TV
	friend.UserRemote();
					
	//remove remote operation from friend:
	friend.RemoveRemoteOperation(setTvOn, owner.TV.Model);
					
	//pass function turnOffTv to the friend:
	friend.SetRemoteOperation(setTvOff, owner.TV.Model);
				
	//friend is using the remote to turn TV
	friend.UserRemote();
					
	//instantiate delegate for static function:
	RemoteOperation operationVoid = new RemoteOperation(DoNothing);
					
	friend.RemoveRemoteOperation(setTvOff, "none");
					
	friend.SetRemoteOperation(operationVoid, "none");
					
	friend.UserRemote();
					
	//finish:
	Console.Read();
    }
			
    public static void DoNothing()
    {
	Console.WriteLine( "Nothing in nothing out, no action");
    }
}	

The expected console output:

delegatesoutpub.gif

This code was created only for teaching purposes, please don’t criticize it if you think it lacks professional luster.

Conclusions

It is obvious now that a delegate when instantiated is an object of MulticastDelegate type.
The purpose of this object is to keep reference(s) to one or many methods with a specific signature. This signature is set during the delegate declaration.
Delegate internally has an array of references to each method and when the delegate runs Invoke() method, all the referenced methods are invoked in line. Because it is an object assigned to a variable, we can treat it as a normal variable and pass it as a parameter.

To be continued...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here