简体   繁体   中英

Open instance delegate with unknown target type?

So I'm trying to create an open delegate that doesn't know the type of its target in advance. I am not sure if that explains it correctly, let me show you:

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
}

While Delegate.CreateDelegate(typeof(Func<X, bool>), method); works, but I don't know the type of X at compile time. What I'd like to do, is use typeof(Func<object, bool>) but that's not possible.

I searched and found this article.

I cleaned up some of the code - here's the related bit for me:

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

Now I can write (in INSERT CODE area) method.DelegateForCallMethod<Func<object, bool>>(); and when I call del(x) it would execute x.test() and output False correctly!

The problem is, changing X to be a struct (which is my actual use-case) breaks it! :(

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

(the line is res = ... )

Any idea why this happens? and how to fix it?

Thanks!

Edit: I do not want to use MethodInfo.Invoke. The whole point here is to create a delegate that's much faster to invoke than regular reflection.

Edit: Tracking down the problem, it seems that F0 is failing to create the delegate if X is a struct - It can be verified by calling MethodInfoExtensions.F0<X, object, bool>(method); - if X was a class then no problem!

Edit: Simplified even more, it seems that Delegate.CreateDelegate(typeof(Func<X, bool>), method) fails to bind if X is struct !

Edit: Found this - pretty much the same issue. But the solution implies having a custom delegate with the argument type (in my case X) known at compile time :(

So the problem is that Delegate.CreateDelegate(typeof(Func<X, bool>), method) fails if X is a struct - according to this I should create my own delegate and pass by ref. I did that, it worked, but now it doesn't if I change back to class ! It starts working again for class but not struct if I remove the ref !

So given this startup code:

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

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

Case1 (works if X is class)

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 (works if X is 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));

Now in order to adapt the original code with this, I have to have two versions for the delegates: one with ref, another without. And inside the DelegateForCallMethod function, I see if the DeclaringType for the input method is a struct or class, and use the appropriate delegate type accordingly (which I'm not even sure if it'll work)

Might update to add code if it works.

Appreciate it if someone can explain what's going on.

Edit: Here we go - (definitely not the prettiest - I feel like I'm doing something redundant):

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

Usage:

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

in this line:

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

make the flags appropriate for your method

I'm using similar code to create getter and setter of unknown instance type.

It uses MakeGenericType instead of MakeGenericMethod , which allows to get rid of GetMethod and Invoke in favor of 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);
    }
}

Testing code:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM