Builds a Delegate from MethodInfo?
Asked Answered
F

3

32

After googling and landing on SO and having read this other question

Is it possible to build a correct Delegate from a MethodInfo if you didn't know the number or types of parameters at compile time?

More on this: can this be done elegantly without the use of Reflection.Emit or type builders?

This is sorta a bummer for me because Delegate.CreateDelegate requires me to specify the correct Delegate type as the first parameter or else it would throw exceptions or invoke an incorrect method.

I'm building some ninja gears and this would helps a lot... Thanks!


Here's a generic solution:

/// <summary>
/// Builds a Delegate instance from the supplied MethodInfo object and a target to invoke against.
/// </summary>
public static Delegate ToDelegate(MethodInfo mi, object target)
{
    if (mi == null) throw new ArgumentNullException("mi");

    Type delegateType;

    var typeArgs = mi.GetParameters()
        .Select(p => p.ParameterType)
        .ToList();

    // builds a delegate type
    if (mi.ReturnType == typeof(void)) {
        delegateType = Expression.GetActionType(typeArgs.ToArray());

    } else {
        typeArgs.Add(mi.ReturnType);
        delegateType = Expression.GetFuncType(typeArgs.ToArray());
    }

    // creates a binded delegate if target is supplied
    var result = (target == null)
        ? Delegate.CreateDelegate(delegateType, mi)
        : Delegate.CreateDelegate(delegateType, target, mi);

    return result;
}

Note: I am building a Silverlight application that would replace a built-years-ago javascript application in which I have multiple Javascript interfaces that calls into the same Silverlight [ScriptableMember] method.

All those legacy JS interfaces need to be supported as well as new interface for accessing new features, so something that automatically setups the JS interface and "delegates" the call to the right Silverlight method would helps speed up work a lot.

I can't post code here, so that's the summary.

Fairhaired answered 14/7, 2009 at 10:28 Comment(0)
F
22

To be honest, if you don't know the type at compile time, there isn't a huge amount of benefit in creating a Delegate. You don't want to use DynamicInvoke; it will be about as slow as reflection. The main exception to this is when there is a delegate-type lurking in the shadows, for example when subscribing to an event - in which case EventInfo makes this available.

For info, in .NET 3.5 on Expression, there is:

Expression.GetActionType(params Type[] typeArgs);
Expression.GetFuncType(params Type[] typeArgs)

That might help to an extent:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
static class Program {
    static void Main() {
        DoStuff("Test1");
        DoStuff("Test2");
    }
    static void DoStuff(string methodName) {
        MethodInfo method = typeof(Program).GetMethod(methodName);
        List<Type> args = new List<Type>(
            method.GetParameters().Select(p => p.ParameterType));
        Type delegateType;
        if (method.ReturnType == typeof(void)) {
            delegateType = Expression.GetActionType(args.ToArray());
        } else {
            args.Add(method.ReturnType);
            delegateType = Expression.GetFuncType(args.ToArray());
        }
        Delegate d = Delegate.CreateDelegate(delegateType, null, method);
        Console.WriteLine(d);
    }
    public static void Test1(int i, DateTime when) { }
    public static float Test2(string x) { return 0; }
}
Featly answered 14/7, 2009 at 10:36 Comment(3)
I'm building this to glue together Silverlight [ScriptableMember] and a separate Javascript interface so I don't have to worry about keeping method signatures in sync in both locations.Fairhaired
@ Marc Gravell, I am not able to invoke the delegate created in the above code like d(). After googling i found that dynamicInvoke can be used to invoke the method, which is very slow. pls help. i am new to delegates and events. my requirement is to invoke a method dynamically, number or type of parameters will be known at runtime onlyForedate
@Foredate to invoke it quickly (via Invoke), you need to know the signature of the method in advance, so that you can cast it to the correct type - for example an Action<Foo>, or a Func<Bar, string, int>.Featly
N
9

Why that complicated?

public static Delegate CreateDelegate(this MethodInfo method)
{
    return Delegate.CreateDelegate
    (
        Expression.GetDelegateType
        (
            method.GetParameters()
                .Select(p => p.ParameterType)
                .Concat(new Type[] { method.ReturnType })
                .ToArray()
        ),
        null,
        method
    );   
}

[Side note: I prefixed this method "Create...". "To..." is confusingly as it misleds you to think it's a conversion.]

Nestor answered 28/12, 2010 at 8:43 Comment(2)
The Expression.GetDelegateType method is actually .NET 4 and SL 4 specific. My question was asked prior to the .NET4 and SL4 release. Anyway thanks for answer. Note that you'll need to bind a delegate to a target if the method is an instance method so the binding part is still mandatory.Fairhaired
By passing an object as second parameter (called 'firstArgument' - I'm passing null here) you can specify the object the Delegate is bound to. Or do I miss the point?Nestor
D
7

If you don't know the number or type of parameters in advance, presumably that means you don't know the delegate type you want to create either?

If that's the case, you're stuck in the absolutely general case.

However, for most common cases (no ref/out parameters, few enough parameters to use one of the existing types) you could get away with one of the Func or Action delegates. (.NET 4.0 has Func/Action types for huge numbers of parameters, so really you'd only need to worry about out/ref parameters.) If the method has a non-void return type use Func, otherwise use Action. Work out which type to use based on the number of parameters, e.g.

static readonly Type[] FuncTypes = { typeof(Func), 
    typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), /* etc */ };

Use Type.MakeGenericType using the parameter types and return type to get the right delegate type, then Delegate.CreateDelegate should work.

I don't have time to work up a sample right now, but let me know if you want me to later on.

One question: how are you intending to use this delegate? Something else is going to need to know how to execute it, surely...

Depose answered 14/7, 2009 at 10:34 Comment(4)
ah-ha ... good take on the MakeGenericType + Func ... that'd do it :-)Fairhaired
To avoid your static Type[], consider Expression.GetActionType / Expression.GetFuncType - see post. I would hope that these methods have been extended to include the new .NET 4.0 variants.Featly
I've added the "why" to the questionFairhaired
Cool - didn't know about Expression.GetActionType/GetFuncType. Froody.Depose

© 2022 - 2024 — McMap. All rights reserved.