Reputation: 8337
Any help would be appreciated on this one. I'm trying to implement a dynamic object wrapper over a static type. This wrapper should then allow me to call static functions dynamically at run time.
For example:
dynamic obj = new StaticDynamicWrapper(typeof(MyStaticType));
obj.DoSomething(arg1);
That's the idea. I got some online reference to get it to work on non-generic method and property but ran into various issues when "DoSomething" is actually a generic method.
For example, if the declaration of the "DoSomething" were as follow:
public static RetType DoSomething<TArg>(this TArg arg1);
Or even worse if do something were to have an overload with the following signature...
public static RetType DoSomething<TArg>(this OtherGenericType<AnotherGenericType<TArg>> arg1);
So I need to implement the DynamicObject.TryInvokeMember in such a way that the method invokation is able to infer at runtime, based on the runtime arguments (ie. object[] args), the correct closed generic method of DoSomething. In another word I wanna be able to select the correct overload and determines the correct type arguments to call MakeGenericMethod with, all that at runtime.
The biggest roadblock so far, is figuring out how to map the method's open generic arguments to the closed types arguments declared by the parameter arguments (ie. object[] args). Can anyone out there help me out?
Thanks for your time!
Upvotes: 4
Views: 1389
Reputation: 8337
For anyone out there who needs this feature, but don't wanna or cannot afford adding external dependencies to their project, the following may help you. It's no where near as robust as the impromptu solution, but may be just what you need. It's only good up to 9 arguments (I think, whatever is the max static arguments of System.Func type).
Use it at your own risk. Tested to work in my computer for my purposes only! ;)
public class StaticDynamicWrapper : DynamicObject
{
private Type _type;
public StaticDynamicWrapper(Type type) { _type = type; }
private static readonly IList< Func<Type, string, object[],object>> CallSiteInvokers;
/// <summary>
/// Static initializer. Used to improve performance so we only need to resolve the right Func type, once.
/// </summary>
static StaticDynamicWrapper()
{
CallSiteInvokers = new List< Func< Type, string, object[],object>>();
//Get the max number of arguments allowed by the built in Func types.
var funcTypes = Assembly.GetAssembly(typeof (Func<>)).GetTypes().Where(t => t.Name.StartsWith("Func`"))
.Concat(Assembly.GetAssembly(typeof (Func<,,,,,,,,,,,,>)).GetTypes().Where(t => t.Name.StartsWith("Func`")))
.OrderBy(t => t.GetGenericArguments().Count()).ToArray();
int maxNoOfArgs = funcTypes.Max(t => t.GetGenericArguments().Length) - 2; //We need to subtract 3 from the absolute max to account for the return type and the 2 required parameters: callsite and target type. Plus 1 to offset the indexing
//Index the function calls based on the number of parameters in the arguments.
for(int i = 0; i < maxNoOfArgs; i++)
{
int funcIndex = i + 2;
CallSiteInvokers.Add
(
( type, name ,objects) =>
{
//The call site pre/post fixes.
var funcGenericArguments = new List<Type>() { typeof(CallSite), typeof(object), typeof(object) };
//The argument info collection
var argumentInfoCollection = new List<CSharpArgumentInfo>()
{
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.UseCompileTimeType |
CSharpArgumentInfoFlags.IsStaticType,
null)
};
//Set up the generic arguments for objects passed in.
funcGenericArguments.InsertRange(2,objects.Select(o => o.GetType()));
//Set up the argument info for the inner binder.
argumentInfoCollection.AddRange(objects.Select(o=> CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)));
var innerBinder = Binder.InvokeMember(
CSharpBinderFlags.None, name, null, null, argumentInfoCollection.ToArray()
);
//Dynamically instantiate the generic CallSite, by calling on the "Create" factory method.
var callSite = typeof (CallSite<>)
.MakeGenericType(
funcTypes[funcIndex]
.MakeGenericType(funcGenericArguments.ToArray())
)
.GetMethod("Create")
.Invoke(null,new object[]{innerBinder});
//Dynamically invoke on the callsite target.
object invokingDelegate = callSite.GetType().GetField("Target").GetValue(callSite);
return invokingDelegate.GetType().GetMethod("Invoke").Invoke(invokingDelegate,
new object[]
{
callSite,
type
}.Concat(objects).ToArray());
}
);
}
}
/// <summary>
/// Handle static property accessors.
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
PropertyInfo prop = _type.GetProperty(binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public);
if (prop == null)
{
result = null;
return false;
}
result = prop.GetValue(null, null);
return true;
}
/// <summary>
/// Handle static methods
/// </summary>
/// <param name="binder"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
try
{
result = CallSiteInvokers[args.Length](_type, binder.Name, args);
return true;
}
catch (RuntimeBinderException)
{
result = null;
return false;
}
}
}
Upvotes: 2
Reputation: 244998
By reading the code of ImpromptuInterface mentioned in jbtule's answer, you can do it using DLR something like this:
public override bool TryInvokeMember(
InvokeMemberBinder binder, object[] args, out object result)
{
var innerBinder = Binder.InvokeMember(
CSharpBinderFlags.None, binder.Name, null, null,
new[]
{
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.UseCompileTimeType
| CSharpArgumentInfoFlags.IsStaticType, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
});
var callSite = CallSite<Func<CallSite, object, object, object>>
.Create(innerBinder);
try
{
result = callSite.Target(callSite, m_type, args[0]);
return true;
}
catch (RuntimeBinderException)
{
result = null;
return false;
}
}
This code works only for methods with one argument. If you want to support more of them, you have to put more CSharpArgumentInfo
s into the collection, create CallSite
with the appropriate number of arguments for the Func
delegate and then call it. (ImpromptuInterface
uses a switch
for that.)
Upvotes: 2
Reputation: 31809
The DLR will infer method overloads including generics. The open source framework ImpromptuInterface available via nuget simplifies the dlr calls to single method calls and does all the work for you. Infact it has a base class ImpromptuForwarder that will do this with a small constructor change.
using ImpromptuInterface;
using ImpromptuInterface.Dynamic;
public class StaticDynamicWrapper:ImpromptuForwarder{
public StaticDynamicWrapper(Type target):base(InvokeContext.CreateStatic(target)){
}
}
Upvotes: 2