通过动态推断泛型类型。反射

时间:2011-07-24 19:19:42

标签: c# generics reflection dynamic .net-4.0

任何帮助都将在这一点上受到赞赏。我试图在静态类型上实现动态对象包装器。然后,这个包装器允许我在运行时动态调用静态函数。

例如:

dynamic obj = new StaticDynamicWrapper(typeof(MyStaticType));
obj.DoSomething(arg1);

这个想法。我得到了一些在线参考,让它在非泛型方法和属性上工作,但在" DoSomething"实际上是一种通用的方法。

例如,如果声明" DoSomething"如下:

public static RetType DoSomething<TArg>(this TArg arg1);

或者甚至更糟糕的是,如果有一些事情要重载以下签名......

public static RetType DoSomething<TArg>(this OtherGenericType<AnotherGenericType<TArg>> arg1);

所以我需要实现DynamicObject.TryInvokeMember,方法invokation能够在运行时基于运行时参数(即object [] args)推断,这是DoSomething的正确封闭泛型方法。换句话说,我希望能够选择正确的重载并确定调用MakeGenericMethod的正确类型参数,所有这些都在运行时。

到目前为止,最大的障碍是弄清楚如何将方法的开放泛型参数映射到参数参数声明的闭合类型参数(即object [] args)。那里的任何人都可以帮助我吗?

谢谢你的时间!

3 个答案:

答案 0 :(得分:2)

DLR将推断方法过载,包括泛型。通过nuget提供的开源框架ImpromptuInterface简化了dlr calls to single method calls并为您完成了所有工作。事实上它有一个基类ImpromptuForwarder,它将通过一个小的构造函数更改来完成。

using ImpromptuInterface;
using ImpromptuInterface.Dynamic;
public class StaticDynamicWrapper:ImpromptuForwarder{
    public StaticDynamicWrapper(Type target):base(InvokeContext.CreateStatic(target)){
    }
}

答案 1 :(得分:2)

通过阅读jbtule的答案中提到的ImpromptuInterface代码,您可以使用DLR这样的代码:

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;
    }
}

此代码仅适用于具有一个参数的方法。如果您想支持更多这些内容,则必须在集合中添加更多CSharpArgumentInfo,使用CallSite委托的适当数量的参数创建Func,然后调用它。 (ImpromptuInterface使用switch。)

答案 2 :(得分:2)

对于那些需要此功能但不想或无法为其项目添加外部依赖项的人,以下内容可能会对您有所帮助。它不像即兴解决方案那样强大,但可能正是您所需要的。它最好只有9个参数(我认为,无论System.Func类型的最大静态参数是什么)。

您自己的风险使用它。经测试只能在我的电脑上工作! ;)

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;
        }  
    }

}