让 PostSharp 帮助记录 IF 语句

Have PostSharp help to log IF statements

我正在寻找的行为类似于以下内容:

[IfElseLogging]
public void Foo(string bar)
{
    if (bar == "somedumbstringbecausethisishorriblewayofdoingthings")
    {
        // here the aspect would log that the if statement above was true,
        // and thus took this path
    }
    else
    {
        // here the aspect would log that the if statement above was false,
        // and thus took this path
    }
}

如果有一种方法可以劫持 if 功能,我就可以用 Postsharp 使用的日志记录来装饰它并擦掉我手上的灰尘。我添加到问题中的另一个原因是我不确定我是否清楚我在问什么。

这是一个非常初级的开发人员完成的项目。我的任务不仅是重构,而且记录代码在流程图级别上究竟做了什么。考虑到代码的性质、缺乏描述、糟糕的技术和实践、项目的性质等……以下是我正在努力解决的问题:

所以我希望使用 Postsharp 来基本上给我我的代码路径和单元测试标准,因为我不知道还有什么方法可以得到它。

另外一个想法,是否可以有一个方面在找到 if 语句时触发事件?

您应该继承 OnMethodBoundaryAspect 并覆盖 OnEntry(MethodExecutionArgs args)OnExceptionAspect 并覆盖 OnException(MethodExecutionArgs args)。您可以从 args

获取并比较您的 string bar

使用 PostSharp 进行面向方面的编程将允许您创建拦截器,这些拦截器可以在定义明确的点(例如方法调用和 属性 访问)挂钩到您的代码中。这是通过重写从您的源代码生成的 IL 来完成的。

在您的情况下,您正在寻找特定的 if 语句,但它们在生成的 IL 中不容易识别。以下是为您的示例方法生成的内容:

          nop
          ldarg.1     
          ldstr       "somedumbstringbecausethisishorriblewayofdoingthings"
          call        System.String.op_Equality
          stloc.0
          ldloc.0
          brfalse.s   NotEqual
          nop

**True branch**

          br.s        Return
NotEqual: nop

**False branch**

Return:   ret

但是,这是未优化的版本。优化编译器生成此代码:

          ldarg.1
          ldstr       "somedumbstringbecausethisishorriblewayofdoingthings"
          call        System.String.op_Equality
          brfalse.s   NotEqual

**True branch**

          ret

NotEqual:

**False branch**

           ret

因此,如果您想进行 IL 重写,您可以想象拦截方法调用,查找类似于 ldstr, "...", call System.String.op_Equality, brfalse.s 的 IL 签名,然后重写 IL 以修改代码。然而,这种方法极其脆弱。源代码中的微小差异可能会生成具有不同签名的 IL,并且只有编译器编写者才能真正了解您所寻找的整个 IL 签名范围。如果条件语句涉及多个术语与逻辑运算符的组合,那么确定在何处插入检测代码会变得非常复杂。然后是误报。就个人而言,我认为这不是检测代码的可行策略。

但是,您还有另一种选择。您可以使用 Roslyn 编译源代码并使用 Roslyn 重写器修改原始源代码树。这是源级别的 AOP,而不是在 IL 级别提供 AOP 的 PostSharp。

要重写语法树,您需要从 CSharpSyntaxRewriter 派生 class。此重写器将修改 if 语句,以便覆盖 VisitIfStatement 方法:

class IfStatementRewriter : CSharpSyntaxRewriter {

  public override SyntaxNode VisitIfStatement(IfStatementSyntax ifStatement) {
    var containsMagicString = ifStatement
      .Condition
      .DescendantNodes()
      .Any(
        syntaxNode => syntaxNode.ToString() == @"""somedumbstringbecausethisishorriblewayofdoingthings"""
      );
    if (!containsMagicString)
      // Do not modify if statement.
      return ifStatement;
    // Only look at the "true" part and assume it is a block (has curly braces).
    var block = ifStatement.Statement as BlockSyntax;
    if (block == null)
      // Do not modify if statement.
      return ifStatement;
    // Insert instrumentation code at start of block.
    var instrumentationStatements = CreateInstrumentationStatements("True branch");
    var modifiedStatements = block.Statements.InsertRange(0, instrumentationStatements);
    var modifiedBlock = block.WithStatements(modifiedStatements);
    return ifStatement.WithStatement(modifiedBlock);
  }

}

要插入检测代码 System.Console.WriteLine("True branch");,使用了以下相当复杂的方法:

IEnumerable<StatementSyntax> CreateInstrumentationStatements(String text) {
  return SyntaxFactory.SingletonList<StatementSyntax>(
    SyntaxFactory.ExpressionStatement(
      SyntaxFactory.InvocationExpression(
        SyntaxFactory.MemberAccessExpression(
          SyntaxKind.SimpleMemberAccessExpression,
          SyntaxFactory.MemberAccessExpression(
            SyntaxKind.SimpleMemberAccessExpression,
            SyntaxFactory.IdentifierName("System"),
            SyntaxFactory.IdentifierName("Console")
          )
          .WithOperatorToken(SyntaxFactory.Token(SyntaxKind.DotToken)),
          SyntaxFactory.IdentifierName("WriteLine")
        )
        .WithOperatorToken(SyntaxFactory.Token(SyntaxKind.DotToken))
      )
      .WithArgumentList(
        SyntaxFactory.ArgumentList(
          SyntaxFactory.SeparatedList(
            new[] {
              SyntaxFactory.Argument(
                SyntaxFactory.LiteralExpression(
                  SyntaxKind.StringLiteralExpression,
                  SyntaxFactory.Literal(text)
                )
              )
            }
          )
        )
      )
    )
    .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
  );
}

您现在可以修改语法树:

var root = (CompilationUnitSyntax) syntaxTree.GetRoot();
var rewriter = new IfStatementRewriter();
var rewrittenRoot = rewriter.Visit(root);

您将需要大大扩展代码以更好地过滤 if 语句并处理所有不同的 if 语句可以编写的方式,但与进行 IL 重写相比,这应该给您一个成功率更高。