How to create a delegate from a MethodInfo when method signature cannot be known beforehand?
Asked Answered
V

3

45

I need a method that takes a MethodInfo instance representing a non-generic static method with arbitrary signature and returns a delegate bound to that method that could later be invoked using Delegate.DynamicInvoke method. My first naïve try looked like this:

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        var method = CreateDelegate(typeof (Console).GetMethod("WriteLine", new[] {typeof (string)}));
        method.DynamicInvoke("Hello world");
    }

    static Delegate CreateDelegate(MethodInfo method)
    {
        if (method == null)
        {
            throw new ArgumentNullException("method");
        }

        if (!method.IsStatic)
        {
            throw new ArgumentNullException("method", "The provided method is not static.");
        }

        if (method.ContainsGenericParameters)
        {
            throw new ArgumentException("The provided method contains unassigned generic type parameters.");
        }

        return method.CreateDelegate(typeof(Delegate)); // This does not work: System.ArgumentException: Type must derive from Delegate.
    }
}

I hoped that the MethodInfo.CreateDelegate method could figure out the correct delegate type itself. Well, obviously it cannot. So, how do I create an instance of System.Type representing a delegate with a signature matching the provided MethodInfo instance?

Vicious answered 3/5, 2013 at 17:4 Comment(4)
Why do you want to create a Delegate and use DynamicInvoke? Using DynamicInvoke is a lot slower than MethodInfo.Invoke.Isocrates
@nawfal Nope. A duplicate requires that the question asked here can be answered in the question you mentioned. The asker wants to be able use MethodInfo.CreateDelegate() when the type representing the method signature is not known. In the other question, this is already known to be MyDelegate, and is thus not helpful to this asker's problem.Credits
Who the heck is deleting my comments? Not the first time! Sorry @Credits I can not find which thread I posted here as duplicate, so I cant inspect. If you could post I will appreciate.Scaler
OK found it from google cache: webcache.googleusercontent.com/…. You're right, they are not the same. The title confused me.Scaler
Z
40

You can use System.Linq.Expressions.Expression.GetDelegateType method:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

class Program
{
    static void Main()
    {
        var writeLine = CreateDelegate(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
        writeLine.DynamicInvoke("Hello world");

        var readLine = CreateDelegate(typeof(Console).GetMethod("ReadLine", Type.EmptyTypes));
        writeLine.DynamicInvoke(readLine.DynamicInvoke());
    }

    static Delegate CreateDelegate(MethodInfo method)
    {
        if (method == null)
        {
            throw new ArgumentNullException("method");
        }

        if (!method.IsStatic)
        {
            throw new ArgumentException("The provided method must be static.", "method");
        }

        if (method.IsGenericMethod)
        {
            throw new ArgumentException("The provided method must not be generic.", "method");
        }

        return method.CreateDelegate(Expression.GetDelegateType(
            (from parameter in method.GetParameters() select parameter.ParameterType)
            .Concat(new[] { method.ReturnType })
            .ToArray()));
    }
}

There is probably a copy-paste error in the 2nd check for !method.IsStatic - you shouldn't use ArgumentNullException there. And it is a good style to provide a parameter name as an argument to ArgumentException.

Use method.IsGenericMethod if you want to reject all generic methods and method.ContainsGenericParameters if you want to reject only generic methods having unsubstituted type parameters.

Zoster answered 3/5, 2013 at 17:5 Comment(5)
Nice solution, however for method with out arguments the created delegate type does not reflect the out modifier.Abatement
@Abatement do you know how to get it working for methods with out arguments? Thank youModla
@Modla I didn't find a general solution. In my specific scenario I could manually treat the cases of out/ref arguments in the code that consumes the delegate. Thus I could solve the problem t by passing the original MethodInfo object to the code that uses the delegate, and manually ask for any argument if it's an out parameter (there's an IsOut and IsByRef properties for ParameterInfo).Abatement
@Abatement do you know if there is a way to once the delegate is created, call it without using Dynamic Invoke? I'm interested in refactoring my code to stop using Reflection (MethofInfo.Invoke) to call the method and start using Delegates instead. But I noticed that Dynamic Invoke is even slower than Invoke. My problem is when the method I want to call has Output parameters. I'm going to post a question about this.Modla
For completeness for future readers: I guess you're talking about - #62515954 As you were answered, the problem there is much simpler than what we deal here, as you know the method signature on compile time. Here we're talking about situation in which you don't have this information on compile time, thus need to make all those tricks. For that case, the only solution I found was to build a matching interceptor method in runtime using Expression and compile this method in runtime.Abatement
B
3

You may want to try System.LinQ.Expressions

...
using System.Linq.Expressions;
...

static Delegate CreateMethod(MethodInfo method)
{
    if (method == null)
    {
        throw new ArgumentNullException("method");
    }

    if (!method.IsStatic)
    {
        throw new ArgumentException("The provided method must be static.", "method");
    }

    if (method.IsGenericMethod)
    {
        throw new ArgumentException("The provided method must not be generic.", "method");
    }

    var parameters = method.GetParameters()
                           .Select(p => Expression.Parameter(p.ParameterType, p.Name))
                           .ToArray();
    var call = Expression.Call(null, method, parameters);
    return Expression.Lambda(call, parameters).Compile();
}

and use it later as following

var method = CreateMethod(typeof (Console).GetMethod("WriteLine", new[] {typeof (string)}));
method.DynamicInvoke("Test Test");
Boudreaux answered 3/5, 2013 at 17:16 Comment(3)
This solution comes with a significant overhead: it builds an expression tree, runs an expression tree compiler, generates a dynamic method and creates a delegate to that method. Then, all subsequent calls to the delegate go through this unnecessary proxy dynamic method. It is much better to create a delegate directly bound to the provided MethodInfo instance.Zoster
@OksanaGimmel The whole process is done only to get the delegate. Once you have the delegate reference, it's just a matter of invoking it.Scaler
@nwafal, While it's true that this is optimally done as a one-time initialization per CLR host or AppDomain incarnation, this doesn't detract from Oksana's comment, considering the asker did not indicate how many times the delegate will subsequently be invoked, versus how many distinct delegates of this type a given session requires. And note that, even in the best case of single-init/multiple-use, if this is the only use of Linq.Expressions in the app, you're taking a significant hit to resolve and load excess libraries, and probably at the worst time, during your boot-up.Clinic
P
0

If you wanna create a non-static method, you must to implement a target/instanced object.

/// <summary>
/// Create delegate by methodinfo in target
/// </summary>
/// <param name="method">method info</param>
/// <param name="target">A instance of the object which contains the method where will be execute</param>
/// <returns>delegate or null</returns>
public static Delegate? CreateDelegateWithTarget(MethodInfo? method, object? target)
{
    if (method is null ||
        target is null)
        return null;

    if (method.IsStatic)
        return null;

    if (method.IsGenericMethod)
        return null;

    return method.CreateDelegate(Expression.GetDelegateType(
        (from parameter in method.GetParameters() select parameter.ParameterType)
        .Concat(new[] { method.ReturnType })
        .ToArray()), target);
}
Procurable answered 9/11, 2022 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.