Exploring delegates in C#


Delegates are a kind of type safe function pointers which are actually declared as class derived from System.MulticastDelegate. There are few rules how to write that class. First you must declare .ctor and Invoke methods, if you like to have asynchronous callback involved there are two more methods just for that case BeginInvoke and EndInvoke.

Except for these two or four methods nothing else should be declared and these declarations must be without implementation (empty body). This is what it looks like:

.method public hidebysig specialname rtspecialname
instance
void .ctor(object 'object',native int 'method') runtime managed
{
}
// end of method WildCard::.ctor
.method public hidebysig virtual instance int32
Invoke() runtime managed
{
}
// end of method WildCard::Invoke

If you like you may delete hidebysig and everything will still working. Do not delete anything else. First argument supplied to .ctor is instance of the class that defines the target method and second is pointer to the method to be called. Following is more in conformance with materials available from Microsoft:

.method public specialname rtspecialname
instance
void .ctor(object Instance, void * Method) runtime managed
{
}
// end of method WildCard::.ctor
.method public virtual instance int32
Invoke() runtime managed
{
}
// end of method WildCard::Invoke

Type of Invoke must match type of method to be called, also list of parameters and their respective types must match. BeginInvoke must be of return type System.IAsyncResult. It accepts three parameters:the first is the same as that being used to call Invoke, type of second is System.AsyncCallback and third is the same as first parameter from .ctor. EndInvoke returns and accepts the same as Invoke plus one extra parameter of type System.IAsyncResult. Now we have all the definitions. Since we are planning to use debugger create shortcut in your "  SendTo" folder and point it to "C:\Program Files\Microsoft.NET\FrameworkSDK\GuiDebug\DbgCLR.exe".
As always we will first write some code in high level language and disassemble P.E. to see what is going on.

Save following as example.cs:

.method public specialname rtspecialname
instance
void .ctor(object Instance, void * Method) runtime managed
{
}
// end of method WildCard::.ctor
.method public virtual instance int32
Invoke() runtime managed
{
}
// end of method WildCard::Invoke
using System;
delegate int WildCard();
class Worker
{
int m_w;
public Worker(int w)
{
m_w=w;
}
public int Multiply()
{
return 2*m_w;
}
}
class User
{
public static void Main()
{
int c=2;
Worker w=
new Worker(c);
WildCard v=
new WildCard(w.Multiply);
Console.WriteLine("Argument is {1}\nResult is {0}",v(),c);
}
}

Compile it from command line using "csc example.cs /debug". Executing example.exe from command line should produce:

Argument is 2
Result is 4

Right click example.exe and from "Send To" pick "DbgCLR.exe". Microsoft CLR Debugger will appear without any code loaded. Press F11 to step through code and example.cs will show up.



Microsoft CLR Debugger in action

If you don't have Command Window activate it from View->Other Windows menu. To see Disassembly you can right click on code window and select "Go To Disassembly" or customize debug toolbar. Command Window works very much as Immediate window from VB 6. Typing c=2 and pressing enter will set value of c or typing? c and pressing enter will retrieve value of c into Command Window. Pressing F11 step through code and if you like observe changes in Locals window.
Next we will send example.exe to ILDASM using the same trick described in my article "Modest introduction to IL assembly language". If we dump treeview this is what we get:

__[MOD] C:\Documents and Settings\current\MyDocuments\asm\finale\example.exe

| M A N I F E S T
|___[CLS] User
| | .
class private auto ansi beforefieldinit
| |___[MET] .ctor :
void()
| |___[STM] Main :
void()
|
|___[CLS] WildCard
| | .
class private auto ansi sealed
| | extends [mscorlib]System.MulticastDelegate
| |___[MET] .ctor :
void(object,native int)
| |___[MET] BeginInvoke :
class [mscorlib]System.IAsyncResult(class [mscorlib]System.AsyncCallback,object)
| |___[MET] EndInvoke : int32(
class [mscorlib]System.IAsyncResult)
| |___[MET] Invoke : int32()
|
|___[CLS] Worker
| | .
class private auto ansi beforefieldinit
| |___[FLD] m_w :
private int32
| |___[MET] .ctor :
void(int32)
| |___[MET] Multiply : int32()
|
 

Please note that our target Worker.Multiply is not receiving any parameter so parameter list for Invoke, BeginInvoke and EndInvoke is shorter than usual. Now dump code as dis_example and compile it from command line using "ilasm dis_example /deb". Send dis_example.exe to DbgCLR.exe and press F11. This time dialog box will appear asking you to locate dis_example.il. After pointing to location of dis_example.il debugger will load our file and pressing F11 we can examine flow of program checking from time to time Locals and values in Command Window.  

Locals window towards end of program

And finally we can start altering assembly code. If you like remove BeginInvoke and EndInvoke from WildCard declaration. Compile it and run it. It still works fine. Next I think that it would be nice to have WildCard derived straight from System.Delegate and maybe to change second parameter of .ctor from native int to void* so that it is more like examples available from Microsoft.

Change WildCard declaration to look like this:

.class private auto ansi sealed WildCard
extends [mscorlib]System.Delegate
{
.method
public specialname rtspecialname
instance
void .ctor(object Instance, void * Method) runtime managed
{
}
// end of method WildCard::.ctor
.method public virtual instance int32
Invoke() runtime managed
{
}
// end of method WildCard::Invoke
.method public virtual
instance
class [mscorlib]System.IAsyncResult
BeginInvoke(
class [mscorlib]System.AsyncCallback callback,
object Instance) runtime managed
{
}
// end of method WildCard::BeginInvoke
.method public virtual
instance int32 EndInvoke(
class [mscorlib]System.IAsyncResult result) runtime managed
{
}
// end of method WildCard::EndInvoke
} // end of class WildCard
 

Also change code in Main where you are calling WildCard::.ctor so that your code looks like this:

.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 50 (0x32)
.maxstack 3
.locals ([0] int32 c,[1]
class Worker w,
[2] class WildCard v)
IL_0000: ldc.i4.2
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: newobj instance
void Worker::.ctor(int32)
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000a: ldftn instance int32 Worker::Multiply()
IL_0010: newobj instance
void WildCard::.ctor(object,void *)
IL_0015: stloc.2
IL_0016: ldstr "Argument is {1}\nResult is {0}"
IL_001b: ldloc.2
IL_001c: callvirt instance int32 WildCard::Invoke()
IL_0021: box [mscorlib]System.Int32
IL_0026: ldloc.0
IL_0027: box [mscorlib]System.Int32
IL_002c: call
void [mscorlib]System.Console::WriteLine(string,object,object)
IL_0031: ret
}
// end of method User::Main

If you don't change it that will compile but will generate run time error. Compile it and check using debugger type of WildCard.

If you were reading "C:\Program Files\Microsoft.NET\FrameworkSDK\Tool Developers Guide\docs\Partition II Metadata.doc" there is under 13.6 Delegates following:

"A better design would be to simply have delegate classes derive directly from System.Delegate."

I hope that this article shows how that could be done using ILASM.


Similar Articles