【问题标题】:Can you get a Func<T> (or similar) from a MethodInfo object?你能从 MethodInfo 对象中获得 Func<T> (或类似的)吗?
【发布时间】:2023-04-04 04:35:01
【问题描述】:

我意识到,一般来说,使用反射会影响性能。 (实际上,我自己根本不喜欢反思;这是一个纯粹的学术问题。)

假设存在一些如下所示的类:

public class MyClass {
    public string GetName() {
        return "My Name";
    }
}

请耐心听我说。我知道如果我有一个名为xMyClass 实例,我可以调用x.GetName()。此外,我可以将Func&lt;string&gt; 变量设置为x.GetName

现在这是我的问题。假设我知道上面的类叫做MyClass;我有一些对象,x,但我不知道它是什么。我可以通过这样做来检查该对象是否具有GetName 方法:

MethodInfo getName = x.GetType().GetMethod("GetName");

假设getName 不为空。那么我不能进一步检查getName.ReturnType == typeof(string)getName.GetParameters().Length == 0,在这一点上,我不能确定我的getName 对象所代表的方法可以肯定转换为Func&lt;string&gt;,不知何故?

我意识到有一个MethodInfo.Invoke,我也意识到我总是可以创建一个Func&lt;string&gt;,比如:

Func<string> getNameFunc = () => getName.Invoke(x, null);

我想我要问的是是否有任何方法可以 MethodInfo 对象它所代表的实际方法,从而产生反射的性能成本在进程中,但是之后能够直接调用方法(例如,通过Func&lt;string&gt;或类似的东西)没有性能损失。

我的设想可能是这样的:

// obviously this would throw an exception if GetActualInstanceMethod returned
// something that couldn't be cast to a Func<string>
Func<string> getNameFunc = (Func<string>)getName.GetActualInstanceMethod(x);

(我意识到这不存在;我想知道是否有任何类似的东西。)

【问题讨论】:

  • 在您编辑的 cmets 上 - 您会看到使用此类解决方案的速度大幅提升,因为动态编译的委托和静态编译的委托之间几乎没有区别;一旦排除了编译的开销。自从我“发现”了表达式树的东西以来,我一直在到处使用它们,并且可能会将其作为我在 .Net 3.5 中的#1 功能。在 v4 中它甚至更好,因为您可以编写多语句代码 - 因为 DLR 需要扩展而需要。

标签: .net performance reflection methodinfo func


【解决方案1】:

我最想到的一种方法是使用动态。然后你可以这样:

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}

【讨论】:

    【解决方案2】:

    这种替换了我之前的答案,因为尽管它是一条稍长的路线,但它为您提供了一个快速的方法调用,并且与其他一些答案不同,它允许您通过不同的实例(以防您要遇到多个相同类型的实例)。如果您不想这样,请查看底部的更新(或查看 Ben M 的答案)。

    这是一个满足您需求的测试方法:

    public class TestType
    {
      public string GetName() { return "hello world!"; }
    }
    
    [TestMethod]
    public void TestMethod2()
    {
      object o = new TestType();
    
      var input = Expression.Parameter(typeof(object), "input");
      var method = o.GetType().GetMethod("GetName", 
        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
      //you should check for null *and* make sure the return type is string here.
      Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));
    
      //now build a dynamic bit of code that does this:
      //(object o) => ((TestType)o).GetName();
      Func<object, string> result = Expression.Lambda<Func<object, string>>(
        Expression.Call(Expression.Convert(input, o.GetType()), method), input).Compile();
    
      string str = result(o);
      Assert.AreEqual("hello world!", str);
    }
    

    一旦您构建了一次委托 - 您可以将其缓存在字典中:

    Dictionary<Type, Func<object, string>> _methods;
    

    然后您所做的就是将其添加到字典中,使用传入对象的类型(来自 GetType())作为键。以后,您首先检查字典中是否有一个现成的委托(如果有,则调用它),否则您首先构建它,添加它,然后调用它。

    顺便说一句,这是 DLR 为其动态调度机制所做的事情的高度简化版本(在 C# 术语中,即使用“动态”关键字时)。

    最后

    如果,正如一些人所提到的,您只是想烘焙一个直接绑定到您收到的对象的 Func,那么您可以这样做:

    [TestMethod]
    public void TestMethod3()
    {
      object o = new TestType();
    
      var method = o.GetType().GetMethod("GetName", 
        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
    
      Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));
    
      //this time, we bake Expression.Constant(o) in.
      Func<string> result = Expression.Lambda<Func<string>>(
       Expression.Call(Expression.Constant(o), method)).Compile();
    
      string str = result(); //no parameter this time.
      Assert.AreEqual("hello world!", str);
    }
    

    请注意,一旦表达式树被丢弃,您需要确保 o 保持在范围内,否则您可能会得到一些令人讨厌的结果。最简单的方法是在您的委托的生命周期内保持本地引用(也许在类实例中)。(由于 Ben M 的 cmets 而被删除)

    【讨论】:

    • 伟大的思想都一样。 :-) 不过,有一个问题 - 为什么是演员表?
    • 哦,但是等一下 - 他仍然需要将参数传递给您的 lambda 表达式。我从最初的问题中得出,生成的函数应该绑定到实例而不必传递参数。
    • 您需要强制转换表达式,因为我们将表达式的输入参数定义为“对象”,如果您尝试通过对对象的引用来调用方法信息,则会出现:System.ArgumentException : 在类型“TestProject1.UnitTest1+TestType”上声明的方法“System.String GetName()”不能用“System.Object”类型的实例调用
    • 已添加示例,说明如何生成绑定到实例的委托,以便无需传递任何参数。
    • 如果您想缓存生成的Func 的静态副本,而不是使用Dictionary,您可以按照Marc Gravell's answer to another question 中使用嵌套通用静态类的模式。跨度>
    【解决方案3】:

    是的,这是可能的:

    Func<string> func = (Func<string>)
                         Delegate.CreateDelegate(typeof(Func<string>), getName);
    

    【讨论】:

    • 此答案不区分实例
    • @Ben M - 正是我的想法 - 以及为什么我决定放弃 Delegate.CreateDelegate 操作 - 因为也无法为第一个参数传递正确的参数类型。
    • 公平积分。但是,我认为您的解决方案不一定更好。如果您最终在许多不同的实例上调用动态编译的实例,则每个实例的大量编译成本很可能比简单地调用MethodInfo 更昂贵。如果是这样,就性能而言,您最好将 GetName 设为静态,并引用实例(因此是显式的 this 指针),从而导致 Func&lt;Foo, string。基本上:这取决于,YMMV。
    • 这就是为什么我的解决方案还包括一个允许您传入实例的解决方案,因为显然,关闭特定实例的动态方法只会损害性能。但是,我没有向 OP 推荐这个,因为这取决于他做出选择。
    【解决方案4】:

    您可以构建一个表达式树来表示调用此方法的 lambda,然后对其进行Compile(),这样进一步的调用将与标准编译调用一样快。

    另外,我不久前根据一篇很棒的 MSDN 文章写了this method,该文章使用 IL 生成一个包装器,以比使用 MethodInfo.DynamicInvoke 更快的方式调用任何 MethodInfo,因为一旦生成代码,几乎没有正常通话的开销。

    【讨论】:

      【解决方案5】:

      这是我的答案,通过构建表达式树。与其他答案不同,结果 (getNameFunc) 是一个绑定到原始实例的函数 - 无需将其作为参数传递。

      class Program
      {
          static void Main(string[] args)
          {
              var p = new Program();
              var getNameFunc = GetStringReturningFunc(p, "GetName");
              var name = getNameFunc();
              Debug.Assert(name == p.GetName());
          }
      
          public string GetName()
          {
              return "Bob";
          }
      
          static Func<string> GetStringReturningFunc(object x, string methodName)
          {
              var methodInfo = x.GetType().GetMethod(methodName);
      
              if (methodInfo == null ||
                  methodInfo.ReturnType != typeof(string) ||
                  methodInfo.GetParameters().Length != 0)
              {
                  throw new ArgumentException();
              }
      
              var xRef = Expression.Constant(x);
              var callRef = Expression.Call(xRef, methodInfo);
              var lambda = (Expression<Func<string>>)Expression.Lambda(callRef);
      
              return lambda.Compile();
          }
      }
      

      【讨论】:

      • +1 - 控制台应用程序,而不是测试项目...更好的复制/粘贴值!
      • 很棒的答案,教会了我很多东西(以前没有对表达式树做过很多工作)。我把它给了安德拉斯,因为他的回答来得早一点,但这也很有帮助。谢谢!
      【解决方案6】:

      最简单的方法是通过Delegate.CreateDelegate

      Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
                                                 typeof(Func<string>), x, getName);
      

      请注意,这会将getNameFunc 绑定到x,因此您需要为每个x 创建一个新的委托实例。此选项比基于Expression 的示例要简单得多。但是,对于基于表达式的示例,可以一次创建 Func&lt;MyClass, string&gt; getNameFuncForAny,您可以将其重复用于 MyClass 的每个实例。

      要创建这样的 getNameFuncForAny,您需要一个类似的方法

      public Func<MyClass, string> GetInstanceMethod(MethodInfo method)
      {
          ParameterExpression x = Expression.Parameter(typeof(MyClass), "it");
          return Expression.Lambda<Func<MyClass, string>>(
              Expression.Call(x, method), x).Compile();
      }
      

      你可以这样使用:

      Func<MyClass, string> getNameFuncForAny = GetInstanceMethod(getName);
      
      MyClass x1 = new MyClass();
      MyClass x2 = new MyClass();
      
      string result1 = getNameFuncForAny(x1);
      string result2 = getNameFuncForAny(x2);
      

      如果不想绑定Func&lt;MyClass, string&gt;,可以定义

      public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
      {
          ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
          return Expression.Lambda<TDelegate>(
              Expression.Call(x, method), x).Compile();
      }
      

      【讨论】:

      • 也可以Delegate.CreateDelegate (typeof (Func&lt;MyClass, string&gt;), getName) -- 我相信它被称为“开放代表”。
      猜你喜欢
      • 1970-01-01
      • 2010-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-29
      • 1970-01-01
      相关资源
      最近更新 更多