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:
Target
– to keep reference of the object where the Method is implemented
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 delegate
s 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 Delegate
s of RemoteOperation
object.
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 ()
:
- It has this function signature.
- It knows where this function resides.
- 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.
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 Delegate
s.
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();
public class Television
{
private string model;
public string Model { get { return model; } }
public Television( string model)
{
this.model = model;
}
public void TurnOn()
{
Console.WriteLine( this.model + " is on");
}
public void TurnOff()
{
Console.WriteLine( this.model + " is off");
}
}
TV owner class has 2 readonly properties TV
and Name
.
public class TvOwner
{
private Television myTV;
private string name;
public string Name { get { return name; } }
public Television TV { get { return myTV; } }
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:
SetRemoteOperation
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.
public class Friend
{
private RemoteOperation remote;
private string name;
public Friend( string name)
{
this.name = name;
}
public void SetRemoteOperation( RemoteOperation remote, string deviceName)
{
this.remote += remote;
Console.WriteLine( "RemoteOperation for " + deviceName + " set");
}
public void RemoveRemoteOperation( RemoteOperation remote, string deviceName)
{
this.remote -= remote;
Console.WriteLine( "RemoteOperation " + deviceName + " removed");
}
public void UserRemote()
{
if ( null != remote)
{
Console.WriteLine(name + " clicked remote '" + remote.GetType().FullName
+ " " + remote.Method.Name + "'");
remote();
}
}
}
Program to run:
public class Program
{
static void Main( string[] args)
{
TvOwner owner = new TvOwner( "Ed", "Samsung");
TvOwner nowner = new TvOwner( "Nick", "Sony");
Friend friend = new Friend( "Alex");
RemoteOperation setTvOn = new RemoteOperation(owner.TV.TurnOn);
RemoteOperation aaa = new RemoteOperation(nowner.TV.TurnOn);
RemoteOperation setTvOff = new RemoteOperation(owner.TV.TurnOff);
friend.SetRemoteOperation(setTvOn, owner.TV.Model);
friend.SetRemoteOperation(aaa, nowner.TV.Model);
friend.UserRemote();
friend.RemoveRemoteOperation(setTvOn, owner.TV.Model);
friend.SetRemoteOperation(setTvOff, owner.TV.Model);
friend.UserRemote();
RemoteOperation operationVoid = new RemoteOperation(DoNothing);
friend.RemoveRemoteOperation(setTvOff, "none");
friend.SetRemoteOperation(operationVoid, "none");
friend.UserRemote();
Console.Read();
}
public static void DoNothing()
{
Console.WriteLine( "Nothing in nothing out, no action");
}
}
The expected console output:
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...