Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 :(

like image 226
vexe Avatar asked Jun 30 '26 10:06

vexe


1 Answers

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));
like image 90
vexe Avatar answered Jul 03 '26 00:07

vexe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!