Working with Delegates
We all are well aware about functions. In conventional way, we create object and we call object.method(). We know that they perform some given task and returns results based on the parameters we passed to them. Now please look at the following cases.
Why do we need delegates?
Case 1:
You have a business class where you have some functions F1, F2, F3 (with similar signatures). You have completed the project and you are using function F2 in so many places, around 100 locations. Now there is a change request that you are supposed to use F3 and not F2. Now the only way you have is, to go back to those 100 places and find/replace your F2 with F3.
Wouldn't it be awesome if you have somebody to represent the function F2, so that you can just tell him not to execute F2 and instead he has to make use of function F3? In such case, the change has to be made only in ONE location - to the representative alone. Yes, delegates are your functional representatives. They are nothing but functional pointers. They are nothing but command objects that will work based on your commands. Delegate represents strongly typed functions.
Case 2:
What if you want to execute those three functions one by one? What if you want your representative to take care of more than one method? Multicast delegates will give you a hand. We will discuss this later.
Case 3:
Assume your function F1 takes long time to execute. You are forced to wait until it gets completed, before you are allowed to proceed with function F2. You can cross this hurdle by making use of asynchronous call with the help of delegates.
Case 4:
You are in a situation where you wanted to pass a method as a parameter to another method. There you have delegates that can be treated as a data type as others do.
Case 5:
Assume you are in a situation where you want the parameters of your methods to be treated in different logics. The logic that needs to be used has to be passed through your front layer. To make it simple, you have to pass two numbers to a function and the logic of treatment, i.e. whether it has to be added or subtracted or multiplied; has to be decided by the front layer. Here delegates are most welcome.
Method and Target
Every delegate has its own method and target properties. The prime idea of this is nothing but to invoke the method in target object.
Simple Approach
Assume there are two functions for a class say, DelegateManager (dm).
- public void SayHello(string input)
- public void Display (string display)
Note they both take string parameter and both of them are void method. In other words, they both have similar signature. You can represent this by a delegate.
public
delegate string MyDelegate(string str);
Instances of this delegate can represent any of these two methods.
// using same delegate to execute both the functions
MyDelegate
d1 = new MyDelegate(dm.SayHello);
MyDelegate
d2 = new MyDelegate(dm.Display);
Note down there is no parenthesis while declaring the functions. We are passing functional pointer to the delegate. You can just invoke the delegate by,
d1("abc");
d2("cde");
Every instance of the delegate will represent the given method. MyDelegate cannot represent any other class, which does not match the type.
As we discussed in case 4, you can also treat methods (i.e. delegates) as data types. You can pass them anywhere you wish to.
Inversion of Controls – IOC
Let us look at a class our business control where I need to add 100 and 25. I am going to make use of the Add method of a Calculator class.
Business Control:
public
delegate int CalculatorHandler(int x, int y);
public
class business
{
public int CallMe(CalculatorHandler ch)
{
// Note i am making use of Calculator class
// without its reference
int result = ch(100,25);
return result;
}
}
Front layer:
private
void button2_Click(object sender, EventArgs e)
{
Calculator
c = new Calculator();
BusinessControl.business bc = new BusinessControl.business();
BusinessControl.CalculatorHandler ch = new BusinessControl.CalculatorHandler(c.Add);
// IOC technique
int Result = bc.CallMe(ch);
MessageBox.Show(Result.ToString());
}
This technique is called IOC (Inversion of Control), i.e. the control is injected at runtime. We are using the 'bc' business control to which the delegate has to be passed.
You can toggle between methods of similar signatures by changing the function pointer. If you want to work with Divide method, just replace c.Add with c.Divide, that’s it.
Usage in Real time
Let us assume your business class needs to call n number of functions f1, f2, f3 & f4 in a specific order.
Assume there is a change request, that you want to insert a new function between f2 and f3 and f4 to be replaced with f5. All you have in front is to do mere Replace.
Instead if I had followed IOC, the business class is not related to any of the functions, in turn it executes delegates.
A mediator will create instance of business rules and then pass it on to business logic. If you run business control with specific business rule, the best way is to use mediator pattern.
Multicast delegates
As we have discussed in the earlier session, delegates represents functional pointers. When a delegate represents multiple methods, it is multi cast delegates.
Multicast delegates should represent void methods. By adding += I am making this delegate to multicast. The moment I add this += it becomes an array !. This would hold as many numbers of methods as you specify.
// Normal delegate
Calculator c = new Calculator();
BusinessControl bc = new BusinessControl();
CalculatorHandler ch = new CalculatorHandler(c.Add);
MessageBox.Show(bc.CallMe(ch).ToString());
//call add method - > this technique is called IOC
// multi cast delegate
Calculator c = new Calculator();
BusinessControl bc = new BusinessControl();
CalculatorHandler ch = new CalculatorHandler(c.Add);
ch += new CalculatorHandler(c.Add);
ch += new CalculatorHandler(c.Multiply);
MessageBox.Show(bc.CallMe(ch).ToString());
Here is how it works. When you invoke the delegate, 'Add' method will be called first followed by 'Multiply' in a single thread and the compiler will come back to you once it is done with all methods specified in the multicast delegate.
Note that you will get a response back only after completing both the tasks ie. Add and Multiply. This is the reason it is suggested that Multicast delegates should represent only void methods.
Hope this might have given you an idea of multicast delegates. Let us look at what is Synchronized and Asynchronized communications and how delegates support these features.
Synchronized Communication
In our previous example, when we call Add() method of calculator class, using oCalc.Add(), we are making a synchronized communication. Meaning, we are waiting for Calculator object to complete its Add method. To test this, add a Thread.Sleep(10000) in the add method. When we call this method, the current thread will be in sleep mode for 10000 milliseconds. In other words, we are forced to wait until the compiler is done with the Add method.
public int Add(int a, int b)
{
Thread.Sleep(10000);
Console.WriteLine((a + b).ToString());
return (a + b);
}
It is not possible to call any other method untill 10000 ms is completed. This is called synchronous communication.
Practical implementation: Think of a complex stored-procedure that takes 10000 ms to execute. We cannot proceed with next step until the procedure is completed and the compiler gets back to the code.
Asynchronous Communication
Asynchronous methods will not wait for the completion of current thread, and hence we can proceed with next.
This is something like we delegate the work to a third person to do the task on our behalf.
Having said this, how about getting a notification from that person once he completes the tasks? Sounds good isn't it? We will discuss about this later.
To try asynchronous methods, we are adding Thread.Sleep(10000) to both Add() and Multiple() methods of Calculator class. This is nothing but to demonstrate the waiting period. Please refer to the below sections.
Synchronize Call - Normal
Calculator c = new Calculator();
BusinessControl bc = new BusinessControl();
CalculatorHandler ch = new CalculatorHandler(c.Add);
MessageBox.Show(bc.CallMe(ch).ToString());
You will notice that, until it is completed we won't be able to do any other task and the entire application would be locked. Try typing something in the textbox during this invocation.
Converting to Asynchronize Call
Calculator c = new Calculator();
CalculatorHandler ch = new CalculatorHandler(c.Add);
ch.BeginInvoke(100, 25, null, null);
ch = new CalculatorHandler(c.Multiply);
ch.BeginInvoke(25, 4, null, null);
Now type something in a textbox.
What is Begin Invoke
The moment you call BeginInvoke, you are creating a fork. Meaning, each delegate will be executed in different threads through BeginInvoke.
The current thread will be splitted into three. One will represent the main thread from which the delegates are called, one will go for the C.ADD method and the other one will be used for the C.MULTIPLY method.
What is End Invoke
EndInvoke is nothing but just opposite action of BeginInvoke. This will create a Join ie, it will wait for all other threads to complete and combine them all together into one single thread and proceed with the Main thread.
Getting results from Asynchronous call - Long running processes
Here I would like to introduce a new member IAsynchResult interface. It takes responsibility to get the work done by the delegate and bring the result back to your location. IAsynchResult.IsCompleted returns true when the asynchronous job is completed; until that time it is false and you can continue with your work. Hence we can use this to assess whether the long running process is completed.
private IAsyncResult ar1 = ch.BeginInvoke(100, 25, null, null);
int Counter = 0;
while (!ar1.IsCompleted)
{
// Do other stuffs
Counter++;
}
// Now we know that call is completed as IsCompleted has returned true
textBox1.Text = Counter.ToString() + " times i was doing my other work";
intResult = (int)ch.EndInvoke(ar1);
MessageBox.Show(intResult.ToString());
}
Now what about Out parameters?
So how are we supposed to deal with out parameters of our method? Let us take a look at the Add method which takes a parameter int as Out. Here's how it goes,
Int Add(Int,int,out int).
Then use,
BeginInvoke(int,int,null,null) and the EndInvoke will be EndInvoke(out int).
Call Back Mechanism - Completion Notification
The idea is to make the calculator come back and say I have finished my work and this is the result. This mechanism is called Callback.
void button4_Click(object sender, EventArgs e)
{
Int32 intResult;
Calculator c = new Calculator();
CalculatorHandler ch = new CalculatorHandler(c.Add);
As we have discussed in the earlier session, delegates represents functional pointers. When a delegate represents multiple methods, it is multi cast delegates.
Multicast delegates should represent void methods. By adding += I am making this delegate to multicast. The moment I add this += it becomes an array !. This would hold as many numbers of methods as you specify.
// Normal delegate
Calculator c = new Calculator();
BusinessControl bc = new BusinessControl();
CalculatorHandler ch = new CalculatorHandler(c.Add);
MessageBox.Show(bc.CallMe(ch).ToString());
//call add method - > this technique is called IOC
// multi cast delegate
Calculator c = new Calculator();
BusinessControl bc = new BusinessControl();
CalculatorHandler ch = new CalculatorHandler(c.Add);
ch += new CalculatorHandler(c.Add);
ch += new CalculatorHandler(c.Multiply);
MessageBox.Show(bc.CallMe(ch).ToString());
Here is how it works. When you invoke the delegate, 'Add' method will be called first followed by 'Multiply' in a single thread and the compiler will come back to you once it is done with all methods specified in the multicast delegate.
Note that you will get a response back only after completing both the tasks ie. Add and Multiply. This is the reason it is suggested that Multicast delegates should represent only void methods.
Hope this might have given you an idea of multicast delegates. Let us look at what is Synchronized and Asynchronized communications and how delegates support these features.
Synchronized Communication
In our previous example, when we call Add() method of calculator class, using oCalc.Add(), we are making a synchronized communication. Meaning, we are waiting for Calculator object to complete its Add method. To test this, add a Thread.Sleep(10000) in the add method. When we call this method, the current thread will be in sleep mode for 10000 milliseconds. In other words, we are forced to wait until the compiler is done with the Add method.
public int Add(int a, int b)
{
Thread.Sleep(10000);
Console.WriteLine((a + b).ToString());
return (a + b);
}
It is not possible to call any other method untill 10000 ms is completed. This is called synchronous communication.
Practical implementation: Think of a complex stored-procedure that takes 10000 ms to execute. We cannot proceed with next step until the procedure is completed and the compiler gets back to the code.
Asynchronous Communication
Asynchronous methods will not wait for the completion of current thread, and hence we can proceed with next.
This is something like we delegate the work to a third person to do the task on our behalf.
Having said this, how about getting a notification from that person once he completes the tasks? Sounds good isn't it? We will discuss about this later.
To try asynchronous methods, we are adding Thread.Sleep(10000) to both Add() and Multiple() methods of Calculator class. This is nothing but to demonstrate the waiting period. Please refer to the below sections.
Synchronize Call - Normal
Calculator c = new Calculator();
BusinessControl bc = new BusinessControl();
CalculatorHandler ch = new CalculatorHandler(c.Add);
MessageBox.Show(bc.CallMe(ch).ToString());
You will notice that, until it is completed we won't be able to do any other task and the entire application would be locked. Try typing something in the textbox during this invocation.
Converting to Asynchronize Call
Calculator c = new Calculator();
CalculatorHandler ch = new CalculatorHandler(c.Add);
ch.BeginInvoke(100, 25, null, null);
ch = new CalculatorHandler(c.Multiply);
ch.BeginInvoke(25, 4, null, null);
Now type something in a textbox.
What is Begin Invoke
The moment you call BeginInvoke, you are creating a fork. Meaning, each delegate will be executed in different threads through BeginInvoke.
The current thread will be splitted into three. One will represent the main thread from which the delegates are called, one will go for the C.ADD method and the other one will be used for the C.MULTIPLY method.
What is End Invoke
EndInvoke is nothing but just opposite action of BeginInvoke. This will create a Join ie, it will wait for all other threads to complete and combine them all together into one single thread and proceed with the Main thread.
Getting results from Asynchronous call - Long running processes
Here I would like to introduce a new member IAsynchResult interface. It takes responsibility to get the work done by the delegate and bring the result back to your location. IAsynchResult.IsCompleted returns true when the asynchronous job is completed; until that time it is false and you can continue with your work. Hence we can use this to assess whether the long running process is completed.
private IAsyncResult ar1 = ch.BeginInvoke(100, 25, null, null);
int Counter = 0;
while (!ar1.IsCompleted)
{
// Do other stuffs
Counter++;
}
// Now we know that call is completed as IsCompleted has returned true
textBox1.Text = Counter.ToString() + " times i was doing my other work";
intResult = (int)ch.EndInvoke(ar1);
MessageBox.Show(intResult.ToString());
}
Now what about Out parameters?
So how are we supposed to deal with out parameters of our method? Let us take a look at the Add method which takes a parameter int as Out. Here's how it goes,
Int Add(Int,int,out int).
Then use,
BeginInvoke(int,int,null,null) and the EndInvoke will be EndInvoke(out int).
Call Back Mechanism - Completion Notification
The idea is to make the calculator come back and say I have finished my work and this is the result. This mechanism is called Callback.
void button4_Click(object sender, EventArgs e)
{
Int32 intResult;
Calculator c = new Calculator();
CalculatorHandler ch = new CalculatorHandler(c.Add);