如何在编译之前修改代码?

时间:2015-10-24 08:21:24

标签: c# roslyn

使用Roslyn,我想在实际编译之前修改我的C#代码。现在,我只需要:

[MyAnotatedMethod]
public void MyMethod() 
{
    // method-body 
}

基于注释,我想在方法的开头和方法的最后注入一些代码。

我知道PostSharp,但这不是我想要的。

这可能与Roslyn有关吗?如果是的话,你能给我一个例子吗?

3 个答案:

答案 0 :(得分:19)

这是一种快速而肮脏的方式来做你想做的事。它基于上述评论之一,指向SebbyLive。这只是一个概念证明,我不会尝试在生产中使用它。

基本思想是更改要修改的项目的编译器。这个改变了的编译器将执行代码注入。因此,您需要编写一个新的编译器(AopCompiler.exe)并将其设置为项目中的构建工具。

将AopCompiler.exe设置为构建工具很简单,在项目文件中,您需要添加以下两行:

<CscToolPath>$(SolutionDir)AopCompiler\bin\Debug</CscToolPath>
<CscToolExe>AopCompiler.exe</CscToolExe>

AopCompiler应该是一个简单的控制台应用程序。这也是代码修改和编译。 如果您不想修改源代码,只需构建它,那么最简单的方法是自己调用csc.exe:

static void Main(string[] args)
{
  var p = Process.Start(@"C:\Program Files (x86)\MSBuild\14.0\Bin\csc.exe", 
            string.Join(" ", args));
  p.WaitForExit();
}

因此,如果您将此设置到目前为止,您将拥有正常的构建过程,而没有编织方面。

此时,如果查看args中的内容,您将看到.RSP文件的文件路径,其中包含csc.exe的所有命令行参数。当然,这些参数也包含所有.CS文件名。因此,您可以解析此.RSP文件并查找所有.CS文件,这些都是编译的一部分。

使用C#文件,可以使用Roslyn完成重写。 CSharpSyntaxRewriter上有很多教程,例如herehere。您需要编写自定义CSharpSyntaxRewriter,它会检查给定的属性,然后将日志记录添加到找到的方法的开头。将记录添加到每个方法的末尾有点棘手,因为可能有多个退出点。要找到这些,您可以使用控制流分析。内置的Roslyn控制流分析可以准确地为您提供所需的内容,ExitPoints属性将一组语句保存在跳转到该区域之外的区域内。

要获得语义模型(然后进行CFG分析),您可以执行以下操作:

public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
    var semanticModel = _compilation.GetSemanticModel(node.SyntaxTree);
    // semanticModel.AnalyzeControlFlow(node.Block)
    return node;
}

最后,要处理每个输入文件AopCompiler,您只需在树的根上调用重写器的Visit方法即可。这将生成修改后的树,您可以将其写入文件。 (您可以修改原始文件,或将结果写入新文件,并相应地更改.RSP文件。)

很抱歉没有提供完整的解决方案,但我希望,这足以让您入门。

答案 1 :(得分:8)

正如我在评论中指出的那样,它目前无法使用。虽然您可以使用hereRoslyn Scripting API显示的AOP技术将某些内容放在一起,以提供非常灵活的解决方案。

目前正确的答案是罗斯林团队有一个公开的提案/问题来支持它。感谢.Net Foundation和Microsoft参与开源,您可以在这里阅读有关此功能的开发:

https://github.com/dotnet/roslyn/issues/5561

答案 2 :(得分:3)

它比使用Annotation更脏一点,但使用预处理器指令阻止代码将允许您根据标志配置代码的编译方式。

http://www.codeproject.com/Articles/304175/Preprocessor-Directives-in-Csharp

如果定义了MyFlag,则只会编译以下#ifMyFlag中的代码

#define MyFlag
public void MyMethod() 
{
    #if MyFlag
        // Flag conditional code

    #endif

    // method-body 
}