Quick example on using the C# 4.0 dynamic keyword

Posted: (EET/GMT+2)

 

One of the new features in the C# 4.0 language that comes with Visual Studio 2010 is the support for the dynamic keyword. This new keyword allows you to work better with for instance scripting languages, but also with for instance COM/OLE automation objects, such as those implemented by Office applications.

However, if you wish to try your skills with the new keyword, it can be difficult to find a simple example. Furthermore, you will need to initialize a dynamic object before you can use it, just like with regular objects. But with which class/type should you initialize a dynamic variable with?

One good starting point if the new type called ExpandoObject. This type is new to .NET 4.0 and can be found from the namespace System.Dynamic. In addition, you you need to reference an assembly named Microsoft.CSharp.dll; this gives you access to the Microsoft.CSharp.RuntimeBinder namespace.

Note that the ExpandoObject has (almost) nothing to do with the Expando class (http://msdn.microsoft.com/en-us/library/microsoft.jscript.expando.aspx) that has been available since .NET 1.0 in the Microsoft.JScript namespace.

Now to the code example. Here is a very quick way to test drive the dynamic keyword:

using System.Dynamic;
...
dynamic d = new ExpandoObject();
d.SomePropertyNameYouveCreated = "ABC";
MessageBox.Show(d.SomePropertyNameYouveCreated);

When you run this code, you've created a dynamic variable d to which you can assign new property values at will. Note that you can come with any (valid) name you like; the compiler will be happy about it. You can also assign any object type to the property name you invent. But if you try to read a property value that does not exist, a runtime (not a compile-time) exception of type RuntimeBinderException (full name Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) will be raised with the message "System.Dynamic.ExpandoObject does not contain a definition for 'SomePropertyName'".

If you disassemble an application with the above code with Red Gate's latest Reflector (it supports .NET 4.0 already), then you can see that the code looks the following:

object d = new ExpandoObject();
if (<button1_Click>o__SiteContainer0.<>p__Site1 == null)
{
    <button1_Click>o__SiteContainer0.<>p__Site1 = CallSite<Func<
	    CallSite, object, string, object>>.Create(Binder.SetMember(
	    CSharpBinderFlags.None, "SomePropertyNameYouveCreated",
	    typeof(Form1), new CSharpArgumentInfo[] {
	    CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
	    CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant |
	    CSharpArgumentInfoFlags.UseCompileTimeType, null) }));
}
<button1_Click>o__SiteContainer0.<>p__Site1.Target.Invoke(
  <button1_Click>o__SiteContainer0.<>p__Site1, d, "ABC");
if (<button1_Click>o__SiteContainer0.<>p__Site2 == null)
{
    <button1_Click>o__SiteContainer0.<>p__Site2 = CallSite<Action<
      CallSite, Type, object>>.Create(Binder.InvokeMember(
      CSharpBinderFlags.ResultDiscarded, "Show", null, typeof(Form1),
      new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(
      CSharpArgumentInfoFlags.IsStaticType |
      CSharpArgumentInfoFlags.UseCompileTimeType, null),
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
}
if (<button1_Click>o__SiteContainer0.<>p__Site3 == null)
{
    <button1_Click>o__SiteContainer0.<>p__Site3 = CallSite<Func<
      CallSite, object, object>>.Create(Binder.GetMember(
      CSharpBinderFlags.None, "SomePropertyNameYouveCreated",
      typeof(Form1), new CSharpArgumentInfo[] {
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
}
<button1_Click>o__SiteContainer0.<>p__Site2.Target.Invoke(
  <button1_Click>o__SiteContainer0.<>p__Site2, typeof(MessageBox),
  <button1_Click>o__SiteContainer0.<>p__Site3.Target.Invoke(
  <button1_Click>o__SiteContainer0.<>p__Site3, d));

In CIL code, the disassembly is (lines splitted for readability):

.method private hidebysig instance void button1_Click(object sender, class
[mscorlib]System.EventArgs e) cil managed
{
  .maxstack 10
  .locals init (
      [0] object d,
      [1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.
          CSharpArgumentInfo[] CS$0$0000)
  L_0000: nop 
  L_0001: newobj instance void [System.Core]System.Dynamic.ExpandoObject::.ctor()
  L_0006: stloc.0 
  L_0007: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`4<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, string, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site1
  L_000c: brtrue.s L_004b
  L_000e: ldc.i4.0 
  L_000f: ldstr "SomePropertyNameYouveCreated"
  L_0014: ldtoken WindowsFormsApplication10.Form1
  L_0019: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(
          valuetype [mscorlib]System.RuntimeTypeHandle)
  L_001e: ldc.i4.2 
  L_001f: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  L_0024: stloc.1 
  L_0025: ldloc.1 
  L_0026: ldc.i4.0 
  L_0027: ldc.i4.0 
  L_0028: ldnull 
  L_0029: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(
          valuetype [Microsoft.CSharp]
          Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
  L_002e: stelem.ref 
  L_002f: ldloc.1 
  L_0030: ldc.i4.1 
  L_0031: ldc.i4.3 
  L_0032: ldnull 
  L_0033: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo 
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(
          valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.
          CSharpArgumentInfoFlags, string)
  L_0038: stelem.ref 
  L_0039: ldloc.1 
  L_003a: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::SetMember(valuetype
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string,
          class [mscorlib]System.Type, class [mscorlib]System.Collections.
          Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.
          RuntimeBinder.CSharpArgumentInfo>)
  L_003f: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0>
          [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib] System.Func`4<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, string, object>>::Create(
          class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
  L_0044: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`4<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, string, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site1
  L_0049: br.s L_004b
  L_004b: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`4<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, string, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site1
  L_0050: ldfld !0 [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`4<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, string, object>>::Target
  L_0055: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`4<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, string, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site1
  L_005a: ldloc.0 
  L_005b: ldstr "ABC"
  L_0060: callvirt instance !3 [mscorlib]System.Func`4<class [System.Core]
          System.Runtime.CompilerServices.CallSite, object, string,
          object>::Invoke(!0, !1, !2)
  L_0065: pop 
  L_0066: 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>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site2
  L_006b: brtrue.s L_00b0
  L_006d: ldc.i4 0x100
  L_0072: ldstr "Show"
  L_0077: ldnull 
  L_0078: ldtoken WindowsFormsApplication10.Form1
  L_007d: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(
          valuetype [mscorlib]System.RuntimeTypeHandle)
  L_0082: ldc.i4.2 
  L_0083: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  L_0088: stloc.1 
  L_0089: ldloc.1 
  L_008a: ldc.i4.0 
  L_008b: ldc.i4.s 0x21
  L_008d: ldnull 
  L_008e: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(
          valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.
          CSharpArgumentInfoFlags, string)
  L_0093: stelem.ref 
  L_0094: ldloc.1 
  L_0095: ldc.i4.1 
  L_0096: ldc.i4.0 
  L_0097: ldnull 
  L_0098: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(
          valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.
          CSharpArgumentInfoFlags, string)
  L_009d: stelem.ref 
  L_009e: ldloc.1 
  L_009f: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(
          valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
          string, class [mscorlib]System.Collections.Generic.IEnumerable`1<
          class [mscorlib]System.Type>, class [mscorlib]System.Type, class
          [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]
          Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
  L_00a4: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0>
          [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]
          System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,
          class [mscorlib]System.Type, object>>::Create(class [System.Core]System.
          Runtime.CompilerServices.CallSiteBinder)
  L_00a9: stsfld 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>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site2
  L_00ae: br.s L_00b0
  L_00b0: 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>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site2
  L_00b5: ldfld !0 [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Action`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, class [mscorlib]System.Type, object>>::Target
  L_00ba: 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>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site2
  L_00bf: ldtoken [System.Windows.Forms]System.Windows.Forms.MessageBox
  L_00c4: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(
          valuetype [mscorlib]System.RuntimeTypeHandle)
  L_00c9: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site3
  L_00ce: brtrue.s L_0103
  L_00d0: ldc.i4.0 
  L_00d1: ldstr "SomePropertyNameYouveCreated"
  L_00d6: ldtoken WindowsFormsApplication10.Form1
  L_00db: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(
          valuetype [mscorlib]System.RuntimeTypeHandle)
  L_00e0: ldc.i4.1 
  L_00e1: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  L_00e6: stloc.1 
  L_00e7: ldloc.1 
  L_00e8: ldc.i4.0 
  L_00e9: ldc.i4.0 
  L_00ea: ldnull 
  L_00eb: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(
          valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.
          CSharpArgumentInfoFlags, string)
  L_00f0: stelem.ref 
  L_00f1: ldloc.1 
  L_00f2: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder
          [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::GetMember(
          valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
          string, class [mscorlib]System.Type, class [mscorlib]System.Collections.
          Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.
          RuntimeBinder.CSharpArgumentInfo>)
  L_00f7: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0>
          [System.Core]System.Runtime.CompilerServices.CallSite`1<class
          [mscorlib]System.Func`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, object>>::Create(class [System.Core]
          System.Runtime.CompilerServices.CallSiteBinder)
  L_00fc: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site3
  L_0101: br.s L_0103
  L_0103: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site3
  L_0108: ldfld !0 [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, object>>::Target
  L_010d: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<
          class [mscorlib]System.Func`3<class [System.Core]System.Runtime.
          CompilerServices.CallSite, object, object>>
          WindowsFormsApplication10.Form1/<button1_Click>o__SiteContainer0::<>p__Site3
  L_0112: ldloc.0 
  L_0113: callvirt instance !2 [mscorlib]System.Func`3<class
          [System.Core]System.Runtime.CompilerServices.CallSite,
          object, object>::Invoke(!0, !1)
  L_0118: callvirt instance void [mscorlib]System.Action`3<class
          [System.Core]System.Runtime.CompilerServices.CallSite, class
          [mscorlib]System.Type, object>::Invoke(!0, !1, !2)
  L_011d: nop 
  L_011e: ret 
}

Lots of things going, that's for sure!

One additional note about Visual Studio 2010 and the improved multi-platform targeting feature: if you try to use the dynamic keyword in a project that you have mistakenly selected as being targeted to .NET 3.5 or earlier, the code will not compile. This is okay and correct. However, if you later change the target platform to 4.0, your code should compile, but this might not always be the case.

Visual Studio 2010 RTM version has only been available just over a week, so I haven't yet had the time to investigate this issue fully, but it appears that Visual Studio will not automatically reload the project, even though the warning message that pops up when you change the target framework says so. It appears (I might be incorrect here, though) that the project is reloaded automatically only if the project is the only project in the currently active solution. If there is more than project in the solution, the project will not be reloaded.

In any case, if the project is not reloaded, the compiler will not be able to correctly compile (build) the project with the new .NET 4.0 and C# 4.0 language features. Thus it is important that you always make sure to reload (or close and re-open) the project if you need to change the target framework.