获取在操作中执行的方法的自定义属性

Get custom attribute of method executed in action

这是我的示例方法

[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<Action>,也不知道方法名称。

任何建议都会有所帮助。

works perfectly, but when I execute it using lambda expression

LogStep(() => DoSomething()) It tells me that there are no attributes of type TestStepAttribute in that Action.

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

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 方法的多个重载,这样您就可以避免在手写代码中进行显式转换。

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

为了处理这两种情况,您需要 LogStep 的两个重载。但是,您不能同时将 LogStep(Action)LogStep(Expression<Action>) 作为重载,因为调用会产生歧义。但如果其中之一是 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 表达式,以防您需要执行该方法。