打开具有未知目标类型的实例委托?

时间:2015-02-01 21:53:12

标签: c# reflection delegates

所以我试图创建一个不提前知道其目标类型的开放代表。我不确定这是否正确解释,让我告诉你:

class X
{
    public bool test() { return false; }
}

static void Main()
{
    var x = new X();
    var runtimeType = x.GetType();
    var method = runtimeType.GetMethod("test");
    var del = ... INSERT CODE
    Console.WriteLine(del(x)); // should output False
}

虽然Delegate.CreateDelegate(typeof(Func<X, bool>), method);有效,但我在编译时不知道X的类型。我想做的是使用typeof(Func<object, bool>),但这是不可能的。

我搜索并找到this文章。

我清理了一些代码 - 这是我的相关内容:

public static class MethodInfoExtensions
{
        public static Func<TArg0, TReturn> F0<T, TArg0, TReturn>(MethodInfo method)
            where T : TArg0
        {
            var d = (Func<T, TReturn>)Delegate.CreateDelegate(typeof(Func<T, TReturn>), method);
            return delegate(TArg0 target) { return d((T)target); };
        }

        public static T DelegateForCallMethod<T>(this MethodInfo targetMethod)
        {
            //string creatorName = (targetMethod.ReturnType == typeof(void) ? "A" : "F") + targetMethod.GetParameters().Length.ToString();
            // this will just do in my case
            string creatorName = "F0";

            var methodParams = targetMethod.GetParameters();
            var typeGenArgs = typeof(T).GetGenericArguments();

            var signature = new Type[1 + methodParams.Length + typeGenArgs.Length];

            int idx = 0;
            signature[idx++] = targetMethod.DeclaringType;

            for (int i = 0; i < methodParams.Length; i++)
                signature[idx++] = methodParams[i].ParameterType;

            for (int i = 0; i < typeGenArgs.Length; i++)
                signature[idx++] = typeGenArgs[i];

            var mth = typeof(MethodInfoExtensions).GetMethod(creatorName, BindingFlags.NonPublic | BindingFlags.Static);
            var gen = mth.MakeGenericMethod(signature);
            var res = gen.Invoke(null, new object[] { targetMethod });
            return (T)res;
        }
}

现在我可以编写(在INSERT CODE区域中)method.DelegateForCallMethod<Func<object, bool>>();,当我调用del(x)时,它会执行x.test()并正确输出False

问题是,将X更改为struct(这是我的实际用例)会破坏它! :(

Unhandled Exception: System.Reflection.TargetInvocationException: Exception has
been thrown by the target of an invocation. ---> System.ArgumentException: Error
 binding to target method. at System.Delegate.CreateDelegate(Type type, MethodInfo method, Boolean throw
OnBindFailure) at Vexe.Runtime.Extensions.VexeTypeExtensions.F0[T,TArg0,TReturn](MethodInfo method) in c:\Users\vexe\Desktop\MyExtensionsAndHelpers\Source\Runtime\RuntimeExtensions\TypeExtensions.cs:line 24
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] argum
ents, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle
 typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invoke
Attr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisib
ilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invoke
Attr, Binder binder, Object[] parameters, CultureInfo culture)
 at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
 at Vexe.Runtime.Extensions.VexeTypeExtensions.DelegateForCallMethod[T](Method
Info targetMethod) in c:\Users\vexe\Desktop\MyExtensionsAndHelpers\Source\Runtime\RuntimeExtensions\TypeExtensions.cs:line 50
   at Program.Main(String[] args) in c:\Users\vexe\Desktop\MyExtensionsAndHelpers\Solution\Test\Program2.cs:line 225

(该行为res = ...

知道为什么会这样吗?以及如何解决它?

谢谢!

编辑:想要使用MethodInfo.Invoke。这里的重点是创建一个委托,它比常规反射更快地调用。

修改:追踪问题,如果F0Xstruct似乎无法创建代理 - 可以通过致电MethodInfoExtensions.F0<X, object, bool>(method); - 如果Xclass,则没问题!

修改简化甚至更多,如果Delegate.CreateDelegate(typeof(Func<X, bool>), method)Xstruct似乎无法绑定!

编辑找到this - 几乎同样的问题。但是解决方案意味着在编译时使用参数类型(在我的情况下为X)中具有自定义委托:(

3 个答案:

答案 0 :(得分:1)

问题是Delegate.CreateDelegate(typeof(Func<X, bool>), method)如果Xstruct则失败 - 根据this我应该创建自己的委托并通过ref传递。我做到了,它有效,但现在如果我改回class则不行!如果我删除了class,它会再次开始为struct而不是ref开始工作!

所以给出了这个启动代码:

class X
{
    public bool test() { return false; }
}

var x = new X();
var runtimeType = x.GetType();
var method = runtimeType.GetMethod("test");

Case1(如果X是班级,则有效)

delegate TReturn MyDelegate1<TArg0, TReturn>(TArg0 obj);

var del = Delegate.CreateDelegate(typeof(MyDelegate1<X, bool>), method) as MyDelegate1<X, bool>;
Console.WriteLine(del(x));

Case2(如果X是struct,则有效)

delegate TReturn MyDelegate2<TArg0, TReturn>(ref TArg0 obj);

var del = Delegate.CreateDelegate(typeof(MyDelegate2<X, bool>), method) as MyDelegate2<X, bool>;
Console.WriteLine(del(ref x));

现在为了使原始代码适应这种情况,我必须有两个代表版本:一个用ref,另一个没有。在DelegateForCallMethod函数中,我看到输入方法的DeclaringType是结构还是类,并相应地使用相应的委托类型(我甚至不确定它是否有效)

如果有效,可能会更新以添加代码。

如果有人能解释发生了什么,请欣赏它。

编辑:我们走了 - (绝对不是最漂亮的 - 我觉得我在做一些多余的事情):

    public delegate TReturn MethodInvoker<TArg0, TReturn>(TArg0 target);
    public delegate TReturn MethodInvokerRef<TArg0, TReturn>(ref TArg0 target);

    public static MethodInvoker<TArg0, TReturn> F0Class<T, TArg0, TReturn>(MethodInfo method)
        where T : TArg0
    {
        var d = Delegate.CreateDelegate(typeof(MethodInvoker<T, TReturn>), method) as MethodInvoker<T, TReturn>;
        return delegate(TArg0 target)
        {
            return d((T)target);
        };
    }

    public static MethodInvokerRef<TArg0, TReturn> F0Struct<T, TArg0, TReturn>(MethodInfo method)
        where T : TArg0
    {
        var d = Delegate.CreateDelegate(typeof(MethodInvokerRef<T, TReturn>), method) as MethodInvokerRef<T, TReturn>;
        return delegate(ref TArg0 target)
        {
            var typed = (T)target;
            return d(ref typed);
        };
    }

    public static Func<TArg0, TReturn> DelegateForCallMethod<TArg0, TReturn>(this MethodInfo targetMethod)
    {
        var declType = targetMethod.DeclaringType;

        var signature = new Type[3]
        {
            declType,
            typeof(TArg0),
            typeof(TReturn)
        };

        bool isValueType = declType.IsValueType;

        string delegateCreator;
        if (isValueType)
            delegateCreator = "F0Struct";
        else
            delegateCreator = "F0Class";


        var mth = typeof(VexeTypeExtensions).GetMethod(delegateCreator, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
        var gen = mth.MakeGenericMethod(signature);
        var res = gen.Invoke(null, new object[] { targetMethod });

        if (isValueType)
        { 
           var mir = (MethodInvokerRef<TArg, TReturn>)res;
           return x => mir(ref x);
        }

        var mi = (MethodInvoker<TArg, TReturn>)res;
        return x => mi(x);
    }

用法:

var x = // ... usual startup code
var del = method.DelegateForCallMethod<object, bool>();
Console.WriteLine(del(x));

答案 1 :(得分:0)

在这一行:

  var mth = typeof(MethodInfoExtensions).GetMethod(creatorName, BindingFlags.NonPublic | BindingFlags.Static);

使标志适合您的方法

答案 2 :(得分:0)

我正在使用类似的代码来创建未知实例类型的getter和setter。

它使用MakeGenericType代替MakeGenericMethod,这样可以摆脱GetMethodInvoke,转而使用Activator.CreateInstance

using System;
using System.Reflection;

public static class GetterSetterHelper
{
    abstract class Factory<T, TValue>
    {
        public abstract Func<T, TValue> CreateGetter(MethodInfo method);
        public abstract Action<T, TValue> CreateSetter(MethodInfo method);

        public static Factory<T, TValue> Create(Type runtimeType)
        {
            var genericType = runtimeType.IsValueType ? typeof(StructFactory<,,>) : typeof(Factory<,,>);
            var factoryType = genericType.MakeGenericType(new Type[] { runtimeType, typeof(T), typeof(TValue) });
            return (Factory<T, TValue>)Activator.CreateInstance(factoryType);
        }
    }

    class Factory<TRuntime, T, TValue> : Factory<T, TValue>
        where TRuntime : class, T
    {
        public override Func<T, TValue> CreateGetter(MethodInfo method)
        {
            var d = (Func<TRuntime, TValue>)Delegate.CreateDelegate(typeof(Func<TRuntime, TValue>), method);
            return delegate (T target) { return d((TRuntime)target); };
        }

        public override Action<T, TValue> CreateSetter(MethodInfo method)
        {
            var d = (Action<TRuntime, TValue>)Delegate.CreateDelegate(typeof(Action<TRuntime, TValue>), method);
            return delegate (T target, TValue value) { d((TRuntime)target, value); };
        }
    }

    class StructFactory<TRuntime, T, TValue> : Factory<T, TValue>
        where TRuntime : struct, T
    {
        delegate TValue GetterDelegate(ref TRuntime instance);

        public override Func<T, TValue> CreateGetter(MethodInfo method)
        {
            var d = (GetterDelegate)Delegate.CreateDelegate(typeof(GetterDelegate), method);
            return delegate (T target)
            {
                var inst = (TRuntime)target;
                return d(ref inst);
            };
        }

        public override Action<T, TValue> CreateSetter(MethodInfo method)
        {
            // It makes little sense to create setter which sets value to COPY of value type
            // It would make sense if we use delegate like:
            // void ActionRef<T, TValue(ref T inst, TValue value);
            throw new NotSupportedException();
        }
    }

    public static Func<T, TValue> CreateGetter<T, TValue>(this MethodInfo methodInfo)
    {
        return Factory<T, TValue>.Create(methodInfo.ReflectedType).CreateGetter(methodInfo);
    }

    public static Action<T, TValue> CreateSetter<T, TValue>(this MethodInfo methodInfo)
    {
        return Factory<T, TValue>.Create(methodInfo.ReflectedType).CreateSetter(methodInfo);
    }
}

测试代码:

using System;

class Program
{
    class Test
    {
        public int DoSomething() { return 1; }
    }

    struct TestStruct
    {
        public int DoSomething() { return 2; }
    }

    static void Main(string[] args)
    {
        var method = typeof(Test).GetMethod("DoSomething");
        var getter = method.CreateGetter<object, int>();
        Console.WriteLine(getter(new Test()));

        var method2 = typeof(TestStruct).GetMethod("DoSomething");
        var getter2 = method2.CreateGetter<object, int>();
        Console.WriteLine(getter2(new TestStruct()));

        Console.ReadKey();
    }
}