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

24,097

Solution 1

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.

Solution 2

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");
Share:
24,097
Zakharia Stanley
Author by

Zakharia Stanley

Updated on July 09, 2022

Comments

  • Zakharia Stanley
    Zakharia Stanley almost 2 years

    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?

  • Oksana Gimmel
    Oksana Gimmel about 11 years
    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.
  • nawfal
    nawfal over 10 years
    @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.
  • Glenn Slayden
    Glenn Slayden over 7 years
    @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.
  • MaMazav
    MaMazav over 5 years
    Nice solution, however for method with out arguments the created delegate type does not reflect the out modifier.
  • fobisthename
    fobisthename almost 4 years
    @MaMazav do you know how to get it working for methods with out arguments? Thank you
  • MaMazav
    MaMazav almost 4 years
    @fobisthename 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).
  • fobisthename
    fobisthename almost 4 years
    @MaMazav 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.
  • MaMazav
    MaMazav almost 4 years
    For completeness for future readers: I guess you're talking about - stackoverflow.com/questions/62515454/… 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.