简体   繁体   中英

Delegate.CreateDelegate is unable to bind to static generic methods

I'm trying to use Delegate.CreateDelegate [MSDN link] to bind to a static generic method, but the binding fails. Here is the PoC code:

public static class CreateDelegateTest {
    public static void Main() {
        Action actionMethod = CreateDelegateTest.GetActionDelegate();
        Action<int> intActionMethod = CreateDelegateTest.GetActionDelegate<int>();
        Func<int> intFunctionMethod = CreateDelegateTest.GetFunctionDelegate<int>();
    }

    public static Action GetActionDelegate() {
        return (Action)Delegate.CreateDelegate(typeof(Action), typeof(CreateDelegateTest), "ActionMethod");
    }

    public static Action<T> GetActionDelegate<T>() {
        return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), typeof(CreateDelegateTest), "GenericActionMethod");
    }

    public static Func<TResult> GetFunctionDelegate<TResult>() {
        return (Func<TResult>)Delegate.CreateDelegate(typeof(Func<TResult>), typeof(CreateDelegateTest), "GenericFunctionMethod");
    }

    public static void ActionMethod() { }

    public static void GenericActionMethod<T>(T arg) { }

    public static TResult GenericFunctionMethod<TResult>() {
        return default(TResult);
    }
}

The actionMethod is created properly, but the intActionMethod and intFunctionMethod creation throws.

Why is CreateDelegate unable to bind to generic methods? How to bind to them?

I've submitted the bug on Microsoft Connect [link] . If you think that this is a bug, vote for it, please.

Update 2: I was wrong thinking that binding to the non-function generic methods succeeds. It turned out that any generic method fails to bind.

Try this (I couldn't get your version of GetActionDelegate() to work also):

public class CreateDelegateTest
{
    public static Func<TResult> GetFunctionDelegate<TResult>()
    {
        var methodInfo = typeof(CreateDelegateTest).GetMethod("FunctionMethod")
                                                   .MakeGenericMethod(typeof(TResult));
        return (Func<TResult>)Delegate.CreateDelegate(typeof(Func<TResult>), methodInfo);
    }

    public static Action<T> GetActionDelegate<T>()
    {
        var methodInfo = typeof(CreateDelegateTest).GetMethod("ActionMethod")
                                                   .MakeGenericMethod(typeof(T));
        return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), methodInfo);
    }
}

I'm not sure why CreateDelegate(Type, Type, string) overload fails to do this, it is just implemented this way.

Update:

It is still possible to use same approach with any delegate type. Main idea is to find correct arguments for MakeGenericMethod() call. Quick example of how it can be done:

public static Delegate CreateDelegate(Type delegateType, Type objectType, string methodName)
{
    var delegateMethod      = delegateType.GetMethod("Invoke");
    var delegateReturn      = delegateMethod.ReturnType;
    var delegateParameters  = delegateMethod.GetParameters();
    var methods             = objectType.GetMethods();
    MethodInfo method = null;
    ParameterInfo[] methodParameters = null;
    Type methodReturn = null;
    // find correct method by argument count
    foreach(var methodInfo in methods)
    {
        if(methodInfo.Name != methodName)
        {
            continue;
        }
        methodParameters = methodInfo.GetParameters();
        methodReturn = methodInfo.ReturnType;
        if(methodParameters.Length != delegateParameters.Length)
        {
            continue;
        }
        method = methodInfo;
    }
    if(method == null)
    {
        throw new Exception("Method not found");
    }
    if(method.IsGenericMethodDefinition)
    {
        var genericArguments    = method.GetGenericArguments();
        var genericParameters   = new Type[genericArguments.Length];

        int genericArgumentIndex = Array.IndexOf(genericArguments, methodReturn);
        if(genericArgumentIndex != -1)
        {
            genericParameters[genericArgumentIndex] = delegateReturn;
        }

        for(int i = 0; i < methodParameters.Length; ++i)
        {
            var methodParameter = methodParameters[i];
            genericArgumentIndex = Array.IndexOf(genericArguments, methodParameter.ParameterType);
            if(genericArgumentIndex == -1) continue;
            genericParameters[genericArgumentIndex] = delegateParameters[i].ParameterType;
        }

        if(Array.IndexOf(genericParameters, null) != -1)
        {
            throw new Exception("Failed to resolve some generic parameters.");
        }

        var concreteMethod = method.MakeGenericMethod(genericParameters);
        return Delegate.CreateDelegate(delegateType, concreteMethod);
    }
    else
    {
        return Delegate.CreateDelegate(delegateType, method);
    }
}

Note 1: I have extremely simplified overloaded method resolution in this example - it relies on arguments count only.

Note 2: It is still possible to write a method which canot be wrapped in delegate this way, for example, int Method<T>(string arg) (anything that does not reference generic argument in argument list or as a return value, which is a bad practice anyway).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM