【问题标题】:Delegate.CreateDelegate is unable to bind to static generic methodsDelegate.CreateDelegate 无法绑定到静态泛型方法
【发布时间】:2012-12-11 16:39:08
【问题描述】:

我正在尝试使用Delegate.CreateDelegate [MSDN link] 绑定到静态泛型方法,但绑定失败。 这是PoC代码:

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);
    }
}

actionMethod 已正确创建,但 intActionMethodintFunctionMethod 创建抛出。

为什么CreateDelegate 无法绑定到泛型方法?如何绑定它们?

我已在 Microsoft Connect [link] 上提交了错误。如果您认为这是一个错误,请投票给它。

更新 2: 我错误地认为绑定到非函数泛型方法会成功。原来任何泛型方法都绑定失败。

【问题讨论】:

  • 这不是错误。您通常会依赖编译器的类型推断来创建您要调用的特定方法的实例,即处理特定类型的方法。 CreateDelegate() 不会为您执行此操作,您必须帮助并显式创建该方法。 MethodInfo.MakeGenericMethod() 是必需的。
  • @HansPassant 原来我错误地认为绑定适用于非函数泛型方法。这实际上发生在所有泛型方法中。

标签: c# generics reflection static delegates


【解决方案1】:

试试这个(我无法让你的 GetActionDelegate() 版本也可以工作):

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);
    }
}

我不知道为什么CreateDelegate(Type, Type, string) 重载无法做到这一点,它就是这样实现的。

更新:

仍然可以对任何委托类型使用相同的方法。主要思想是为MakeGenericMethod() 调用找到正确的参数。如何完成的快速示例:

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);
    }
}

注意 1:在这个例子中我已经非常简化了重载方法解析 - 它只依赖于参数计数。

注意 2: 仍然可以编写不能以这种方式包装在委托中的方法,例如,int Method&lt;T&gt;(string arg)(任何不引用参数列表中的泛型参数或作为返回值,无论如何这是一个不好的做法)。

【讨论】:

  • 遗憾的是,我无法通过这种方式解决我的真正问题。在我的真实情况下,我只有一个TDelegate 泛型类型参数,我想为其找到一个静态方法。从中提取所需信息并非易事。
  • 我不认为我理解你的代码的目的(或者至少我确信应该有一些其他的方式来做你想做的事),但仍然可以稍微改进CreateDelegate() 用于泛型方法。答案已更新。
  • 我的代码的目的是将“DynamicInvoke”类型的处理程序 (Func&lt;object[], object&gt;) 公开为普通的严格类型的“Invoke”处理程序。例如,假设我有一些“DynamicInvoke”处理程序,我想将其公开为EventHandler&lt;MyEventArgs&gt;。我只写var adapter = new DynamicInvokeAdapter&lt;EventHandler&lt;MyEventArgs&gt;&gt;(handler),现在我可以写adapter.Invoke(sender, e)。例如,我可以为它订阅一些事件。发布的代码更复杂,因为它使用了一些魔法来静态缓存委托创建工厂,因此每个委托类型只使用一次反射。
  • 您的代码似乎是一个很好的(虽然可以理解的麻烦)Delegate.CreateDelegate 替代品。如果你不介意的话,我还是等一下。也许还有办法修复Delegate.CreateDelegate
猜你喜欢
  • 1970-01-01
  • 2012-12-04
  • 1970-01-01
  • 1970-01-01
  • 2011-10-02
  • 2020-02-27
  • 2011-07-14
  • 2010-10-16
  • 2012-08-26
相关资源
最近更新 更多