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?

nawfal
  • 70,104
  • 56
  • 326
  • 368
Zakharia Stanley
  • 1,196
  • 1
  • 9
  • 10
  • 2
    Why do you want to create a Delegate and use DynamicInvoke? Using DynamicInvoke is a lot slower than MethodInfo.Invoke. – Martin Mulder May 04 '13 at 13:16
  • @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. – einsteinsci Jun 07 '15 at 06:08
  • Who the heck is deleting my comments? Not the first time! Sorry @einsteinsci I can not find which thread I posted here as duplicate, so I cant inspect. If you could post I will appreciate. – nawfal Jun 07 '15 at 10:18
  • OK found it from google cache: http://webcache.googleusercontent.com/search?q=cache:vQJJgP2HX9IJ:stackoverflow.com/questions/16364198. You're right, they are not the same. The title confused me. – nawfal Jun 07 '15 at 10:34

3 Answers3

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.

Oksana Gimmel
  • 937
  • 8
  • 13
  • 1
    Nice solution, however for method with out arguments the created delegate type does not reflect the out modifier. – MaMazav Aug 31 '18 at 13:08
  • @MaMazav do you know how to get it working for methods with out arguments? Thank you – fobisthename Jun 16 '20 at 18:03
  • @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). – MaMazav Jun 18 '20 at 09:03
  • @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. – fobisthename Jun 22 '20 at 13:03
  • For completeness for future readers: I guess you're talking about - https://stackoverflow.com/questions/62515454/create-delegate-from-methodinfo-with-output-parameters-without-dynamic-invoke 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. – MaMazav Jun 25 '20 at 20:43
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");
Khoa Nguyen
  • 1,056
  • 2
  • 12
  • 20
  • 4
    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. – Oksana Gimmel May 03 '13 at 17:22
  • @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. – nawfal Nov 01 '13 at 06:46
  • @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. – Glenn Slayden Jan 08 '17 at 00:07
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);
}