Динамически выводить общие типы через.отражение - PullRequest
4 голосов
/ 24 июля 2011

Любая помощь будет оценена по этому вопросу. Я пытаюсь реализовать оболочку динамического объекта поверх статического типа. Эта обертка должна позволять мне динамически вызывать статические функции во время выполнения.

Например:

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 таким образом, чтобы вызов метода мог выводить во время выполнения на основе аргументов времени выполнения (т. Е. Аргументов object []) правильный корректный обобщенный метод DoSomething. Другими словами, я хочу выбрать правильную перегрузку и определить правильные аргументы типа для вызова MakeGenericMethod, и все это во время выполнения.

Самым большим препятствием на данный момент является выяснение того, как сопоставить открытые универсальные аргументы метода с аргументами закрытых типов, объявленными аргументами параметра (т. Е. Object [] args). Кто-нибудь может мне помочь?

Спасибо за ваше время!

Ответы [ 3 ]

2 голосов
/ 25 июля 2011

Прочитав код ImpromptuInterface, упомянутый в ответе jbtule, вы можете сделать это с помощью 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 голосов
/ 27 июля 2011

Для тех, кто нуждается в этой функции, но не хочет или не может позволить себе добавить внешние зависимости в свой проект, вам может помочь следующее. Это далеко не так надежно, как импровизированное решение, но может быть именно то, что вам нужно. Это только хорошо до 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;
        }  
    }

}
2 голосов
/ 25 июля 2011

DLR будет выводить перегрузки метода, в том числе обобщения.Платформа с открытым исходным кодом ImpromptuInterface , доступная через nuget, упрощает вызовы dlr для вызовов одного метода и выполняет всю работу за вас.Infact у него есть базовый класс ImpromptuForwarder , который будет делать это с небольшим изменением конструктора.

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