Consider the following C# 4 code that creates a null dynamic field and a null object field, then attempts to invoke GetType() through each:
using System;
public class Program
{
private dynamic i;
private object j;
public static void Main()
{
var app = new Program();
try { Console.WriteLine(app.i.GetType()); }
catch (Exception e)
{
Console.WriteLine("{0} says:\r\n\t{1}",
e.GetType(), e.Message);
}
try { Console.WriteLine(app.j.GetType()); }
catch (Exception e)
{
Console.WriteLine("{0} says:\r\n\t{1}",
e.GetType(), e.Message);
}
Console.ReadLine();
}
}
The console output of this small program is the following text:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException says:
Cannot perform runtime binding on a null reference
System.NullReferenceException says:
Object reference not set to an instance of an object.
Why the different treatment between objects and dynamics? After all, we hear all the time that the new dynamic type in C# 4 is really just an object with special runtime behaviors ascribed to it. But the difference in the exception types makes it clear that dynamic types are not plain, old object references. The IL for this code makes it very clear what's going on. I've included the disassembled IL for Main generated by the ILDASM tool below. You'll see that the dynamic field is really a CallSite object. The CallSite class does a lot of interesting things which I'll be blogging about now that the VS2010 Beta 1 is publicly available.
Even if you don't understand what the following code is doing, find the first try block in the code and compare it to the second one. You can see in the second try block that the access to the plain, old object field is very straightforward, just a couple of lines of code. Since the object reference is initialized to null, when the GetType() method is invoked through it, a NullReferenceException is thrown. Standard stuff since .NET 1.0. But in the first try block, where the dynamic field is used to invoke GetType(), the call is dispatched through a compile-time constructed CallSite. So, the CallSite reference is not null which is why we don't get a NullReferenceException. Instead, the CallSite attempts to bind to a real object to dispatch the call and finds that there is nothing there. Hence, the RuntimeBinderException. I think it's important to know that dynamics are not just objects with special runtime behaviors attached to them. I know the following IL code is long and ugly but do this: search in the following text for the word GetType in double quotes. Look at how that string is being passed to the runtime binder in the dynamic case. This is very much like using eval() in JavaScript to inject strings of code into the browser at runtime. C# 4 truly is a dynamic language now.
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 283 (0x11b)
.maxstack 12
.locals init ([0] class Program app,
[1] class [mscorlib]System.Exception e,
[2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
IL_0000: nop
IL_0001: newobj instance void Program::.ctor()
IL_0006: stloc.0
.try
{
IL_0007: nop
IL_0008: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> Program/'<Main>o__SiteContainer0'::'<>p__Site1'
IL_000d: brtrue.s IL_004e
IL_000f: ldc.i4.0
<snip/>
IL_006c: brtrue.s IL_00a2
IL_006e: ldc.i4.0
IL_006f: ldstr "GetType"
IL_0074: ldtoken Program
IL_0079: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_007e: ldnull
IL_007f: ldc.i4.1
IL_0080: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
<snip/>
IL_00df: leave.s IL_00e1
} // end handler
IL_00e1: nop
.try
{
IL_00e2: nop
IL_00e3: ldloc.0
IL_00e4: ldfld object Program::j
IL_00e9: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
IL_00ee: call void [mscorlib]System.Console::WriteLine(object)
<snip/>
IL_0119: pop
IL_011a: ret
} // end of method Program::Main