【问题标题】:Get the name of a method using an expression使用表达式获取方法的名称
【发布时间】:2012-01-03 17:57:28
【问题描述】:

我知道网站上有一些关于这个的答案,如果这有任何重复,我深表歉意,但我发现的所有答案都没有做我想做的事情。

我正在尝试指定方法信息,以便我可以通过不使用字符串以类型安全的方式获取名称。 所以我试图用一个表达式来提取它。

假设我想在这个接口中获取一个方法的名称:

public interface IMyInteface
{
    void DoSomething(string param1, string param2);
}

目前我可以使用此方法获取名称:

 MemberInfo GetMethodInfo<T>(Expression<Action<T>> expression)
 {
        return ((MethodCallExpression)expression.Body).Method;
 }

我可以如下调用辅助方法:

var methodInfo = GetMethodInfo<IMyInteface>(x => x.DoSomething(null, null));
Console.WriteLine(methodInfo.Name);

但我正在寻找不指定参数(null,null)就可以获取方法名称的版本

像这样:

var methodInfo = GetMethodInfo<IMyInteface>(x => x.DoSomething);

但是所有尝试都编译失败

有没有办法做到这一点?

【问题讨论】:

  • 目前没有 VS,但我认为您应该尝试接受 DelegateAction&lt;string, string&gt; 在非通用替代方案中。在这种情况下,您应该能够传递一个方法组
  • 如果我将输入更改为 Delegate,它将无法编译,并且错误是无法将方法组转换为委托。如果我将其更改为 Action 它似乎可以工作,但表达式被转换为 UnaryExpression 并且该方法为空,我无法看到从 UnaryExpression 获取它的位置
  • 转换后..你试过var methodInfo = GetMethodInfo&lt;IMyInteface&gt;(DoSomething);而不是var methodInfo = GetMethodInfo&lt;IMyInteface&gt;(x =&gt; x.DoSomething);吗?另外,我仍然认为指定 (default(string), default(string)) - 更具可读性,并且在这种情况下您可以支持重载方法
  • 我不能通过直接指定做某事来使用 GetMethodInfo(DoSomething) 我需要引用实例,但是没有实例,这就是为什么它必须是一个表达式,IMyInterface 从来没有实际调用过具体实例,但仅用于提取元数据。我不知道我是否有意义

标签: c# expression lambda


【解决方案1】:
x => x.DoSomething

为了使其可编译,我只看到了两种方法:

  1. 采用非通用方式并将其参数指定为Action&lt;string, string&gt;
  2. 自行指定 Action&lt;string, string&gt; 作为您的目标委托类型:GetMethodInfo&lt;IMyInteface&gt;(x =&gt; new Action&lt;string,string&gt;(x.DoSomething))

如果你可以使用第二个,它允许你省略参数,那么你可以编写你的 GetMethodInfo 方法如下:

    MemberInfo GetMethodInfo<T>(Expression<Func<T, Delegate>> expression)
    {
        var unaryExpression = (UnaryExpression) expression.Body;
        var methodCallExpression = (MethodCallExpression) unaryExpression.Operand;
        var methodInfoExpression = (ConstantExpression) methodCallExpression.Arguments.Last();
        var methodInfo = (MemberInfo) methodInfoExpression.Value;
        return methodInfo;
    }

它适用于您的界面,但可能需要一些概括才能使其适用于任何方法,这取决于您。

【讨论】:

  • 我希望能少一些冗长的东西,但是,这很好用,谢谢。
  • 我很好奇,我知道已经有 2 年了,所以我希望你仍然可以回答:有没有办法允许这样的语法:GetMethodInfo&lt;MyInterface&gt;(x =&gt; x.DoSomething);
  • 关于这种方法的说明,这仅适用于 .NET 3.5 和 4.0,不适用于 4.5。在后一种情况下,方法调用表达式的表示方式不同,因此参数顺序也不同。在这种情况下,您必须这样做:var methodInfoExpression = (ConstantExpression)methodCallExpression.Object;。总的来说,我觉得这种方法太麻烦了。
  • @nawfal,表达式树由编译器生成。您的目标是哪个 .Net 版本并不重要。您可能的意思是 C#2012 编译器生成的表达式树与早期版本不同。
  • @nawfal C# 5.0 编译器的行为会因目标框架而异。如果我没看错的话,对于.Net 4.0,它会生成CallDelegate.CreateDelegate,而对于.Net 4.5,它是新的MethodInfo.CreateDelegate
【解决方案2】:

以下与 .NET 4.5 兼容:

public static string MethodName(LambdaExpression expression)
{
    var unaryExpression = (UnaryExpression)expression.Body;
    var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
    var methodCallObject = (ConstantExpression)methodCallExpression.Object;
    var methodInfo = (MethodInfo)methodCallObject.Value;

    return methodInfo.Name;
}

您可以将它与x =&gt; x.DoSomething 之类的表达式一起使用,但是对于不同类型的方法,它需要对通用方法进行一些包装。

这是一个向后兼容的版本:

private static bool IsNET45 = Type.GetType("System.Reflection.ReflectionContext", false) != null;

public static string MethodName(LambdaExpression expression)
{
    var unaryExpression = (UnaryExpression)expression.Body;
    var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
    if (IsNET45)
    {
        var methodCallObject = (ConstantExpression)methodCallExpression.Object;
        var methodInfo = (MethodInfo)methodCallObject.Value;
        return methodInfo.Name;
    }
    else
    {
        var methodInfoExpression = (ConstantExpression)methodCallExpression.Arguments.Last();
        var methodInfo = (MemberInfo)methodInfoExpression.Value;
        return methodInfo.Name;
    }
}

检查this sample code on Ideone。 请注意,Ideone 没有 .NET 4.5。

【讨论】:

    【解决方案3】:

    这个问题是x.DoSomething 代表一个方法组。而且您必须以某种方式明确指定要将方法组转换为哪种委托类型,以便可以选择组的正确成员。该组是否仅包含一个成员也没关系。

    编译器可以推断出你的意思是那个,但它不会那样做。 (我认为是这样,如果您添加该方法的另一个重载,您的代码不会中断。)

    Snowbear 的回答包含有关可能解决方案的好建议。

    【讨论】:

      【解决方案4】:

      这是对旧问题的新答案,但对已接受答案的“冗长”投诉作出回应。它需要更多代码,但结果是这样的语法:

      MemberInfo info = GetActionInfo<IMyInterface, string, string>(x => x.DoSomething);
      

      或者,对于有返回值的方法

      MemberInfo info = GetFuncInfo<IMyInterface, object, string, string>(x => x.DoSomethingWithReturn);  
      

      在哪里

      object DoSomethingWithReturn(string param1, string param2);
      

      就像框架提供最多 16 个参数的 Action 和 Func 代表一样,您必须拥有最多接受 16 个参数的 GetActionInfo 和 GetFuncInfo 方法(尽管我认为重构是明智的,如果你有具有 16 个参数的方法)。更多代码,但语法有所改进。

      【讨论】:

      • 我希望不那么冗长意味着我可以做这样的事情:GetMethodInfo&lt;MyInterface&gt;(x =&gt; x.DoSomething);?不过,我对如何实施您的建议感到有些困惑。你能提供一个快速的代码示例吗?
      • 不传递参数的唯一方法(据我所知)是在通用规范中包含参数类型 - 即 GetMethodInfo(x => x.做某事)。
      【解决方案5】:

      如果您可以使用nameof() 运算符,您可以使用以下方法。

      其中一个好处是不必解开表达式树或提供默认值或担心该方法的类型的非空实例。

      // As extension method
      public static string GetMethodName<T>(this T instance, Func<T, string> nameofMethod) where T : class
      {
          return nameofMethod(instance);
      }
      
      // As static method
      public static string GetMethodName<T>(Func<T, string> nameofMethod) where T : class
      {
          return nameofMethod(default);
      }
      
      

      用法:

      public class Car
      {
          public void Drive() { }
      }
      
      var car = new Car();
      
      string methodName1 = car.GetMethodName(c => nameof(c.Drive));
      
      var nullCar = new Car();
      
      string methodName2 = nullCar.GetMethodName(c => nameof(c.Drive));
      
      string methodName3 = GetMethodName<Car>(c => nameof(c.Drive));
      

      【讨论】:

      • 如果你能强制在参数中使用nameof(),而不是仅仅使用一个字符串。
      【解决方案6】:

      如果您的应用程序允许依赖 Moq(或类似的库),您可以执行以下操作:

      class Program
      {
          static void Main(string[] args)
          {
              var methodName = GetMethodName<IMyInteface>(x => new Action<string,string>(x.DoSomething));
              Console.WriteLine(methodName);
          }
      
          static string GetMethodName<T>(Func<T, Delegate> func) where T : class
          {
              // http://code.google.com/p/moq/
              var moq = new Mock<T>();
              var del = func.Invoke(moq.Object);
              return del.Method.Name;
          }
      }
      
      public interface IMyInteface
      {
          void DoSomething(string param1, string param2);
      }
      

      【讨论】:

      • -1,这给了你正在创建的中间匿名委托的句柄(传递给GetMethodInfo 方法),而不是你试图代表的实际成员的句柄( DoSomething).
      • @nawful 原始发帖人要求输入字符串 Name,而不是原始 MethodInfo。我已经更新了我的示例以澄清。
      • 我担心它是否仍然不能给出正确的结果。你测试过这个吗?
      • 是的,我已经测试过了。
      • 哦,好吧,我看错了,你的编辑是没有根据的:)。在某种程度上,这是一个比公认的解决方案更好的解决方案,所以 +1。但缺点是您需要一个T 实例才能按您的方式工作。不太确定哪个性能更高。感谢添加!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多