【问题标题】:Get custom attribute of method executed in action获取实际执行的方法的自定义属性
【发布时间】:2016-02-12 12:13:21
【问题描述】:

这是我的示例方法

[TestStep("Do something")]
private void DoSomething()
{   
}

每个看起来像上面的方法都以需要记录方法参数的方式执行:

private void LogStep(Action action)
{
    string stepName = "[" + action.Method.Name + "] ";
    var descr = Attribute.GetCustomAttribute(action.Method, typeof(TestStepAttribute)) as TestStepAttribute;

    if (descr == null)
    {
        this.TestLog.AddWarningMessage(
            (action.Method.DeclaringType == null ? string.Empty : action.Method.DeclaringType.FullName + ".") + action.Method.Name
            + ": missing description");

        return;
    }

    stepName += descr.Description;

    this.TestLog.EndGroup();

    this.TestLog.BeginGroup(stepName);
}

我遇到了一个问题。像这样执行 LogStep

LogStep(DoSomething)

完美运行,但是当我使用 lambda 表达式执行它时

LogStep(() => DoSomething())

它告诉我Action 中没有TestStepAttribute 类型的属性。

乍一看,它似乎类似于How do I get the custom attributes of a method from Action<T>?,但就我而言,我既不能将Action 的类型更改为Expression&lt;Action&gt;,也不知道方法名称。

任何建议都会有所帮助。

【问题讨论】:

  • 编写一个接受表达式树的方法版本(与委托不同,它很容易检查)。否则,除非您想开始深入研究 IL 操作码,否则您将不走运。
  • 你的第一个执行示例不应该是LogStep(DoSomething)吗? (没有“()”)

标签: c# lambda action


【解决方案1】:

当您使用 lambda 表达式执行它时,lambda 表达式本身就是方法。碰巧在它的主体中有一个方法调用,但那里可能还有其他东西(如new object())。访问此内部方法的属性的唯一方法是将 lambda 表达式作为Expression 传递并分析该表达式。

为了处理这两种情况,您需要LogStep 的两个重载。但是,您不能同时将 LogStep(Action)LogStep(Expression&lt;Action&gt;) 作为重载,因为调用会模棱两可。但如果其中之一是LogStep(Delegate),它会起作用。

private void LogStep(Delegate action)
{
    var attr = (TestStepAttribute)Attribute
        .GetCustomAttribute(action.Method, typeof(TestStepAttribute));
    Console.WriteLine("LogStep(Delegate action):  " + attr?.Description);
}

private void LogStep(Expression<Action> actionExpr)
{
    string descr = null;
    var methodCall = actionExpr.Body as MethodCallExpression;
    if (methodCall != null) {
        var attribs = methodCall.Method.GetCustomAttributes(typeof(TestStepAttribute), true);
        if (attribs.Length > 0) {
            descr = ((TestStepAttribute)attribs[0]).Description;
        }
    }
    Console.WriteLine("LogStep(Expression<Action> actionExpr):  " + descr);
}

测试:

LogStep(new Action(DoSomething)); // new Action() Is required here. Calls first overlaod.
LogStep(() => DoSomething()); // Calls second overload.
LogStep(() => new object());  // Calls second overload.

请注意,您可以编译和执行 lambda 表达式,以防需要执行该方法。

【讨论】:

    【解决方案2】:

    完美运行,但是当我使用 lambda 表达式执行它时

    LogStep(() => DoSomething()) 它告诉我没有属性 该 Action 中的 TestStepAttribute 类型。

    当然不会找到任何属性,因为您正在传递一个基本上是一个方法的 lambda 表达式,并且在该方法中您传递了您的方法 DoSomething() 并且检查是在 lambda 表达式上完成的。

    【讨论】:

    • 我完全同意你的看法。问题是如何从内部方法中检索该属性
    【解决方案3】:

    Lambda 表达式只是另一种方法。当您查看action.Method 时,这就是您得到的方法(并且action.Target 将包含一个闭包,如果有的话)。

    最后,你只有:

    void SomeAnonymousMethod()
    {
      DoSomething();
    }
    

    要获得实际调用的方法,您必须首先反编译匿名方法。当然,您可能正在使用 lambda 语法来传递参数,同时仍然使用无参数操作,这会变得更加疯狂:

    class SomeClosure
    {
      string argument1;
      int argument2;
    
      void AnonymousMethod()
      {
        var data = GetSomeData(argument2);
    
        DoSomething(data, argument1);
      }
    }
    

    你怎么告诉DoSomething是你需要元数据的方法?

    没有办法使用 lambda 表达式来解决这个问题。幸运的是,无论如何您似乎都不需要它,因为您从不调用参数。不用Action,直接用Delegate,你可以直接传任何你需要的方法:

    void DoSomething(string something, string otherThing)
    {
      ... // Not important
    }
    
    void LogStep(Delegate someDelegate)
    {
      ... // Exactly as before
    }
    
    LogStep((Action<string, string>)DoSomething);
    

    遗憾的是,您必须在调用时手动转换,否则编译器会给您一个错误;不过,您可以为 LogStep 方法本身保留相同的签名。或者,您可以使用一个简单的 T4 模板来创建 LogStep 方法的多个重载,这样您就可以避免在您的手写代码中进行显式转换。

    【讨论】:

      猜你喜欢
      • 2010-11-04
      • 2022-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多