James
James

Reputation: 1691

Does calling a method on a dynamic object basically do GetMethod().Invoke()?

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.

Upvotes: 2

Views: 859

Answers (3)

Ron Beyer
Ron Beyer

Reputation: 11273

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.

Upvotes: 1

Enigmativity
Enigmativity

Reputation: 117084

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.

Upvotes: 1

James Ko
James Ko

Reputation: 34529

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.

Upvotes: 0

Related Questions