2

The title is basically my question. Under the covers does the last line of this:

Type xmlCodecType = typeof(XmlCodec<>).MakeGenericType(typeof(SomeObjectProperty));
dynamic xmlCodec = Activator.CreateInstance(xmlCodecType);
xmlCodec.ReadCollection(xmlCodec.GetCollectionName());

basically do this:

MethodInfo method1 = xmlCodec.GetType().GetMethod("ReadCollection");
MethodInfo method2 = xmlCodec.GetType().GetMethod("GetCollectionName");
method1.Invoke(xmlCodec, new obj[] { method2.Invoke(xmlCodec, null) });

when executed??

Most of what I have written is using the reflection method because it is what I was used to and just feels a bit more 'catch errors during compile time' with the passing of the write types and objects and such. Dynamic is a bit more hands off. However reflection can be harder to read/follow at times whereas while dynamic is not always a keyword in a language, it is common concept across most of them.

James
  • 1,651
  • 2
  • 18
  • 24
  • 3
    _[Understanding the Dynamic Keyword in C# 4](https://msdn.microsoft.com/en-us/magazine/gg598922.aspx)_ –  Apr 30 '15 at 01:28
  • I was about to post the same article that @MickyDuncan did. Check out the sections on Dynamic Method Bags and Class Wrappers, including the Bill Wagner article mentioned: https://msdn.microsoft.com/library/ee658247 – Phil Walton Apr 30 '15 at 01:32
  • 1
    Its a good question and I think one of the few people who can answer this intelligently is @JonSkeet, otherwise what I would do is make a quick really small test program, compile it, then examine it with ILDASM or DotPeek and see what the compiler spits out. Based on the good performance of DLR in C#5, I'm guessing its more than what you posted. – Ron Beyer Apr 30 '15 at 01:36
  • 1
    No, the `dynamic` type results in a fair bit more than just that. Obviously for .NET types, at some level it will eventually reflect and invoke the named member. But the runtime supports other scenarios, and has various optimizations to improve performance over straight, naïve reflection. – Peter Duniho Apr 30 '15 at 01:39
  • Thanks for the Article @MickyDuncan, was a very good read to show me what else I can do with dynamic and DRL but I guess I was looking for more of what is really going on under the hood as eluded to by the other commentators. The implication means that on a general level changing over to the use of dynamic may actually improve the application over using the reflection. I look forward to a potential answer from JonSkeet if possible I guess.... I also keep meaning to get a piece of software to look at the IL generation, but I keep getting busy. – James Apr 30 '15 at 01:45

3 Answers3

1

Here's a simple bit of test code:

void Main()
{
    this.DoSomething();
}

private void DoSomething()
{
    Console.WriteLine("Foo");
}

That compiles to this IL:

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  call        UserQuery.DoSomething
IL_0007:  nop         
IL_0008:  ret         

DoSomething:
IL_0000:  nop         
IL_0001:  ldstr       "Foo"
IL_0006:  call        System.Console.WriteLine
IL_000B:  nop         
IL_000C:  ret         

Now, if I introduce a dynamic reference:

void Main()
{
    dynamic bar = this;
    bar.DoSomething();
}

private void DoSomething()
{
    Console.WriteLine("Foo");
}

Here's the IL:

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  stloc.0     // bar
IL_0003:  ldsfld      UserQuery+<Main>o__SiteContainer0.<>p__Site1
IL_0008:  brtrue.s    IL_0042
IL_000A:  ldc.i4      00 01 00 00 
IL_000F:  ldstr       "DoSomething"
IL_0014:  ldnull      
IL_0015:  ldtoken     UserQuery
IL_001A:  call        System.Type.GetTypeFromHandle
IL_001F:  ldc.i4.1    
IL_0020:  newarr      Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
IL_0025:  stloc.1     // CS$0$0000
IL_0026:  ldloc.1     // CS$0$0000
IL_0027:  ldc.i4.0    
IL_0028:  ldc.i4.0    
IL_0029:  ldnull      
IL_002A:  call        Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create
IL_002F:  stelem.ref  
IL_0030:  ldloc.1     // CS$0$0000
IL_0031:  call        Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember
IL_0036:  call        System.Runtime.CompilerServices.CallSite<System.Action<System.Runtime.CompilerServices.CallSite,System.Object>>.Create
IL_003B:  stsfld      UserQuery+<Main>o__SiteContainer0.<>p__Site1
IL_0040:  br.s        IL_0042
IL_0042:  ldsfld      UserQuery+<Main>o__SiteContainer0.<>p__Site1
IL_0047:  ldfld       System.Runtime.CompilerServices.CallSite<System.Action<System.Runtime.CompilerServices.CallSite,System.Object>>.Target
IL_004C:  ldsfld      UserQuery+<Main>o__SiteContainer0.<>p__Site1
IL_0051:  ldloc.0     // bar
IL_0052:  callvirt    System.Action<System.Runtime.CompilerServices.CallSite,System.Object>.Invoke
IL_0057:  nop         
IL_0058:  ret         

DoSomething:
IL_0000:  nop         
IL_0001:  ldstr       "Foo"
IL_0006:  call        System.Console.WriteLine
IL_000B:  nop         
IL_000C:  ret         

And this decompiles to:

private void Main()
{
    object obj = (object)this;
    if (SiteContainer.Site == null)
        SiteContainer.Site = CallSite<Action<CallSite, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "DoSomething", (IEnumerable<Type>)null, typeof(UserQuery), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[1]
        {
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
        }));
    SiteContainer.Site.Target((CallSite)SiteContainer.Site, obj);
}

private void DoSomething()
{
    Console.WriteLine("Foo");
}

[CompilerGenerated]
private static class SiteContainer
{
    public static CallSite<Action<CallSite, object>> Site;
}

So, that is, as far as I can tell, exactly what is being called.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Ok, So in looking at this and the information from @RonBeyer's answer it would seem that a dynamic method call is going to bind to the SiteContainer a method call of the name provided, an argument list and then set the target to be the object in which to call the method upon. This makes me very interested in what a GetMethod().Invoke() call would look like as well, that appears to be a one to one relationship.. Get a method binding and then invoke it on an object. – James Apr 30 '15 at 17:28
1

Just to add to the other answers, I made the following test (more specific to how you are structuring your code):

public class XmlCodec<T>
{
    public List<string> MyList = new List<string>();

    public string GetCollectionName()
    {
        return "Some Collection Name";
    }

    public void ReadCollection(string collectionName)
    {
        for (int i = 0; i < 100; i++)
            MyList.Add(collectionName + i.ToString());
    }
}

class Program
{

    static void Main(string[] args)
    {
        Type xmlCodecType = typeof(XmlCodec<>).MakeGenericType(typeof(string));
        dynamic xmlCodec = Activator.CreateInstance(xmlCodecType);
        xmlCodec.ReadCollection(xmlCodec.GetCollectionName());

        Print(xmlCodec);
        Console.ReadKey(true);
    }

    public static void Print(dynamic obj)
    {
        foreach (string s in obj.MyList)
            Console.WriteLine(s);
    }

}

Now, in DotPeek, this is what it looks like (Print method omitted for brevity):

private static void Main(string[] args)
{
  object instance = Activator.CreateInstance(typeof (XmlCodec<>).MakeGenericType(typeof (string)));
  // ISSUE: reference to a compiler-generated field
  if (Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site1 == null)
  {
    // ISSUE: reference to a compiler-generated field
    Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site1 = CallSite<Action<CallSite, object, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "ReadCollection", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2]
    {
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null),
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
    }));
  }
  // ISSUE: reference to a compiler-generated field
  Action<CallSite, object, object> action = Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site1.Target;
  // ISSUE: reference to a compiler-generated field
  CallSite<Action<CallSite, object, object>> callSite = Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site1;
  object obj1 = instance;
  // ISSUE: reference to a compiler-generated field
  if (Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site2 == null)
  {
    // ISSUE: reference to a compiler-generated field
    Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site2 = CallSite<Func<CallSite, object, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "GetCollectionName", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[1]
    {
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
    }));
  }
  // ISSUE: reference to a compiler-generated field
  // ISSUE: reference to a compiler-generated field
  object obj2 = Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site2.Target((CallSite) Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site2, instance);
  action((CallSite) callSite, obj1, obj2);
  // ISSUE: reference to a compiler-generated field
  if (Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site3 == null)
  {
    // ISSUE: reference to a compiler-generated field
    Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site3 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Print", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2]
    {
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null),
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
    }));
  }
  // ISSUE: reference to a compiler-generated field
  // ISSUE: reference to a compiler-generated field
  Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site3.Target((CallSite) Program.\u003CMain\u003Eo__SiteContainer0.\u003C\u003Ep__Site3, typeof (Program), instance);
  Console.ReadKey(true);
}

Notice how the type and activation have been consolidated into one line. The rest of the method body (aside from the Print at the bottom) is for calling the two nested methods which look like they have been expanded into two calls.

Ron Beyer
  • 11,003
  • 1
  • 19
  • 37
0

No, a method is not simply looked up with that name. (dynamic is not just limited to methods, although; it can also be used to bind to fields/properties.) The binding expressions can actually be manipulated if the class in question extends DynamicObject. As a simple example:

class DynamicDictionary : DynamicObject {
    private Dictionary<string, object> Dict = /*...*/;

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        result = Dict[binder.Name];

        return true;
    }
}

Now if you ran the following code:

dynamic dict = new DynamicDictionary();
object o = dict.Abc123;

DynamicDictionary, instead of trying to get the field or property dict.Abc123, would attempt to lookup and return Dict["Abc123"].

EDIT: Found a more in-depth post on how dynamic works here.

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 1
    I am not sure how this applies to my situation of comparing the differences of reference calls and a method call on a dynamic object? Could you explain a bit more? – James Apr 30 '15 at 02:25