How can I dynamically call a method on a dynamic object?
Asked Answered
B

2

18

When I want to dynamically call a statically-defined ("statically" in the sense of "determined at compile-time", not in the sense of "class-level member") method on any object in C#, I can use reflection to get a handle to that method and invoke it:

typeof(Foo).GetMethod("Bar").Invoke(foo, new object[] { /* params */ });

However, objects made dynamic by inheriting from DynamicObject respond to (undefined) instance method calls using TryInvokeMember, and the dynamic methods the class responds to are not exposed through reflection, for obvious reasons. This means that I can't get a method handle for a method that should be responded to by TryInvokeMember.

So ironically, it seems to me that you can't dynamically call a dynamic method on a dynamic object as easily as you can call a defined method on a non-dynamic object.

I've considered calling TryInvokeMember directly, but the first argument must be an instance of an InvokeMemberBinder, an abstract class. I feel that if I have to implement a class to call a dynamic method on a dynamic object, I must be doing something wrong.

How can I call a method on a dynamic object by its name, knowing that the target class does not implement it and that it should be responded to using TryInvokeMember?

Burton answered 5/12, 2012 at 0:16 Comment(0)
S
9

One way to go about it is to mimic what the C# compiler outputs for method invocations on dynamic objects. This requires the usage of a bunch of types marked [EditorBrowsable(EditorBrowsableState.Never)] in the Microsoft.CSharp.RuntimeBinder namespace, so they will not be visible in Intellisense. Needless to say, this doesn't seem like a supported scenario, so use it at your own risk!

This code calls the dynamic Bar method without any arguments on an instance of a class derived from DynamicObject:

dynamic dynamicObject = new DerivedFromDynamicObject();
var callSiteBinder = Binder.InvokeMember(CSharpBinderFlags.None, "Bar", Enumerable.Empty<Type>(), typeof(Program),
    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
var callSite = CallSite<Action<CallSite, object>>.Create(callSiteBinder);
callSite.Target(callSite, dynamicObject);

This blog post and this one have more gory details on call sites and binders.

Sweeper answered 5/12, 2012 at 2:42 Comment(3)
Sounds fun. I'll wait to see if someone actually has a supported solution since I probably won't be around anymore when this stops working.Burton
On the other hand, since that's what the compiler already does, it's unlikely to ever stop working, as that would just kill any application using dynamic built today.Burton
@Burton Right, I would think that it's a pretty safe bet. It's just surprising that they went to such great lengths as to ensure the types are hidden.Sweeper
J
15

I have an open source (Apache license) framework Dynamitey (available in nuget) which encapsulates the dynamic binder code, this includes automatically caching the call sites. It has convenience methods for every type of binder too (getters,setters, events, indexers, operators, conversions), but specifically you want InvokeMember.

The dynamic binder code actually runs faster than reflection (amortized) when calling statically defined (at compile time) members of classes too.

Dynamic.InvokeMember(foo,"Bar",arg...);
Jellybean answered 10/12, 2012 at 16:9 Comment(2)
Man! Great nuget! Works perfect! Thank you! should be marked as answer!Bascinet
I am using something like this Dynamic.InvokeGet(o.xp, "Product-Group") but in case Product-Group missing it throw error, any idea how to handle that ?Negligee
S
9

One way to go about it is to mimic what the C# compiler outputs for method invocations on dynamic objects. This requires the usage of a bunch of types marked [EditorBrowsable(EditorBrowsableState.Never)] in the Microsoft.CSharp.RuntimeBinder namespace, so they will not be visible in Intellisense. Needless to say, this doesn't seem like a supported scenario, so use it at your own risk!

This code calls the dynamic Bar method without any arguments on an instance of a class derived from DynamicObject:

dynamic dynamicObject = new DerivedFromDynamicObject();
var callSiteBinder = Binder.InvokeMember(CSharpBinderFlags.None, "Bar", Enumerable.Empty<Type>(), typeof(Program),
    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
var callSite = CallSite<Action<CallSite, object>>.Create(callSiteBinder);
callSite.Target(callSite, dynamicObject);

This blog post and this one have more gory details on call sites and binders.

Sweeper answered 5/12, 2012 at 2:42 Comment(3)
Sounds fun. I'll wait to see if someone actually has a supported solution since I probably won't be around anymore when this stops working.Burton
On the other hand, since that's what the compiler already does, it's unlikely to ever stop working, as that would just kill any application using dynamic built today.Burton
@Burton Right, I would think that it's a pretty safe bet. It's just surprising that they went to such great lengths as to ensure the types are hidden.Sweeper

© 2022 - 2024 — McMap. All rights reserved.