【问题标题】:How to create a delegate from a MethodInfo when method signature cannot be known beforehand?当事先无法知道方法签名时,如何从 MethodInfo 创建委托?
【发布时间】:2013-05-03 17:04:24
【问题描述】:

我需要一个方法,它接受一个 MethodInfo 实例,该实例表示一个具有任意签名的非泛型静态方法,并返回一个绑定到该方法的委托,该方法以后可以使用 Delegate.DynamicInvoke 方法调用。我的第一次天真的尝试是这样的:

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.
    }
}

我希望MethodInfo.CreateDelegate 方法本身可以找出正确的委托类型。好吧,显然它不能。那么,如何创建一个System.Type 的实例来代表一个具有与提供的MethodInfo 实例匹配的签名的委托?

【问题讨论】:

  • 为什么要创建Delegate并使用DynamicInvoke?使用 DynamicInvoke 比 MethodInfo.Invoke 慢很多。
  • @nawfal 不。重复要求可以在您提到的问题中回答此处提出的问题。当表示方法签名的类型未知时,提问者希望能够使用MethodInfo.CreateDelegate()。在另一个问题中,这已经知道是MyDelegate,因此对这个提问者的问题没有帮助。
  • 到底是谁在删除我的 cmets?不是第一次!抱歉@einsteinsci 我找不到我在这里发布的重复帖子,所以我无法检查。如果您能发帖,我将不胜感激。
  • OK 从谷歌缓存中找到它:webcache.googleusercontent.com/…。你是对的,它们不一样。标题让我很困惑。

标签: c# .net reflection delegates methodinfo


【解决方案1】:

你可以使用System.Linq.Expressions.Expression.GetDelegateType方法:

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

在第二次检查 !method.IsStatic 时可能存在复制粘贴错误 - 您不应该在那里使用 ArgumentNullException。提供参数名称作为ArgumentException 的参数是一种很好的风格。

如果您想拒绝所有泛型方法,请使用method.IsGenericMethod,如果您只想拒绝具有未替换类型参数的泛型方法,请使用method.ContainsGenericParameters

【讨论】:

  • 不错的解决方案,但是对于没有参数的方法,创建的委托类型不反映 out 修饰符。
  • @MaMazav 你知道如何让它为没有参数的方法工作吗?谢谢
  • @fobisthename 我没有找到通用解决方案。在我的特定场景中,我可以在使用委托的代码中手动处理 out/ref 参数的情况。因此,我可以通过将原始 MethodInfo 对象传递给使用委托的代码来解决问题,并手动询问任何参数是否是输出参数(ParameterInfo 有 IsOut 和 IsByRef 属性)。
  • @MaMazav 你知道是否有一种方法可以在创建委托后在不使用动态调用的情况下调用它?我有兴趣重构我的代码以停止使用反射 (MethofInfo.Invoke) 来调用该方法并开始使用委托。但我注意到 Dynamic Invoke 甚至比 Invoke 还要慢。我的问题是当我要调用的方法具有输出参数时。我将发布一个关于此的问题。
  • 为了未来读者的完整性:我猜你在谈论 - stackoverflow.com/questions/62515454/… 正如你所回答的那样,那里的问题比我们在这里处理的要简单得多,因为你知道编译时的方法签名时间。在这里,我们谈论的是您没有关于编译时间的这些信息的情况,因此需要制作所有这些技巧。对于这种情况,我找到的唯一解决方案是在运行时使用 Expression 构建匹配的拦截器方法并在运行时编译此方法。
【解决方案2】:

您可能想试试 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();
}

稍后按如下方式使用

var method = CreateMethod(typeof (Console).GetMethod("WriteLine", new[] {typeof (string)}));
method.DynamicInvoke("Test Test");

【讨论】:

  • 这个解决方案带来了很大的开销:它构建一个表达式树,运行一个表达式树编译器,生成一个动态方法并创建该方法的委托。然后,对委托的所有后续调用都会通过这个不必要的代理动态方法。最好创建一个直接绑定到提供的MethodInfo 实例的委托。
  • @OksanaGimmel 整个过程只是为了获得委托。获得委托引用后,只需调用它即可。
  • @nwafal,虽然这确实是作为每个 CLR 主机或 AppDomain 化身的一次性初始化最佳完成,但这并没有减损 Oksana 的评论,考虑到提问者没有说明多少次随后将调用委托,而不是给定会话需要多少这种类型的不同委托。请注意,即使在单初始化/多次使用的最佳情况下,如果这是应用程序中 Linq.Expressions 的唯一用途,您在解析和加载多余的库时会受到很大的影响,并且可能在最糟糕的时候,在你的启动过程中。
猜你喜欢
  • 2011-03-02
  • 2011-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-18
相关资源
最近更新 更多