Common Type System—CTS Support
CTS Support
The actual IL emitted shows some of the complexities of delegates in the underlying type system:
struct MyDelegate : System.MulticastDelegate { public MyDelegate(object target, IntPtr methodPtr); private object target; private IntPtr methodPtr; public internal void Invoke(int x, int y); public internal System.IAsyncResult BeginInvoke(int x, int y, System.IAsyncCallback callback, object state); public internal void EndInvoke (System.IAsyncResult result); }
The constructor is used to form a delegate over a target object and a function pointer. The Invoke,
BeginInvoke, and EndInvoke methods implement the delegate invocation routine and are marked as
internal (i.e., runtime in IL) to indicate that the CLR provides the implementation; their IL bodies are
left blank. Invoke performs a synchronous invocation, while the BeginInvoke and EndInvoke functions
follow the Asynchronous Programming Model pattern for asynchronous
method invocation.
Notice first that the MyDelegate type breaks one of the rules discussed above, namely that structs cannot
derive from types other than ValueType. Delegates have special support in the CTS, so this is
allowed. Also notice that MyDelegate derives from MulticastDelegate; this type is the common base
for all delegates created in C# and supports delegates that have multiple targets. The section on events
describes why this is useful. MulticastDelegate also defines a large set of additional methods that we
will ignore for now and that have been omitted from the definition above.
To form a delegate over a target, the IL uses MyDelegate’s constructor. The constructor accepts an
object for input, which is the this pointer to be passed during method invocation (or null for static
methods), and an IntPtr representing the managed function pointer to the CLR method. This is a C-style
function pointer that points at the just-in-time compiled code in the runtime. The Invoke signature
matches that of the delegate we defined, and the CLR ensures that both statically and dynamically the
function pointer contained within the delegate matches this signature.
The CreateAndInvoke code above emits the following sequence of IL:
ldarg.0 // loads the ‘this’ for CreateAndInvoke’s method ldftn void Foo::PrintPair(int32, int32) newobj instance void MyDelegate::.ctor(object, native int) ldc.i4.s 10 ldc.i4.s 20 callvirt instance void MyDelegate::Invoke(int32, int32) ret
Notice that it uses the ldftn instruction to load a function pointer to the PrintPair method and then
uses the Invoke method defined on MyDelegate to invoke the underlying method. This has the result of
indirectly calling the code for PrintPair, passing the values 10 and 20 for its arguments.
|

