让 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 重写相比,这应该给您一个成功率更高。
我正在寻找的行为类似于以下内容:
[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 重写相比,这应该给您一个成功率更高。