web-dev-qa-db-ja.com

コンパイル前にコードを変更するにはどうすればよいですか?

Roslynを使用して、実際にコンパイルする前にC#コードを変更したいと思います。今のところ、次のようなものが必要です。

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

そして、アノテーションに基づいて、メソッドの最初と最後にコードを挿入したいと思います。

私はPostSharpを知っていますが、それは私が望んでいることではありません。

これはRoslynで行うことは可能ですか?もしそうなら、例を挙げていただけますか?

47
Lukas Durovsky

ここにあなたが望むことをするための迅速で汚い方法があります。これは、上記のコメントの1つに基づいており、 SebbyLive を指しています。これは概念実証にすぎず、本番環境で使用することはしません。

基本的な考え方は、変更するプロジェクトのコンパイラを変更することです。そして、この変更されたコンパイラはコードインジェクションを行います。したがって、新しいコンパイラー(AopCompiler.exe)を作成し、それをプロジェクトのビルドツールとして設定する必要があります。

ビルドツールとしてのAopCompiler.exeの設定は簡単です。プロジェクトファイルで、次の2行を追加する必要があります。

<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の内容を確認すると、csc.exeのすべてのコマンドラインパラメーターを含む.RSPファイルへのファイルパスがあることがわかります。もちろん、これらのパラメータにはすべての.CSファイル名も含まれています。したがって、この.RSPファイルを解析して、コンパイルの一部であるすべての.CSファイルを見つけることができます。

C#ファイルが手元にあれば、Roslynを使用して書き換えを行うことができます。 CSharpSyntaxRewriterには多くのチュートリアルがあります。たとえば、 ここここ です。特定の属性をチェックするカスタム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ファイルを変更することができます。)

完全に機能するソリューションを提供していないことをお詫びしますが、これで十分に始めることができます。

19
Tamas

私のコメントで指摘したように、それは現在利用できません。示されているAOP手法を使用して何かを組み合わせることができますが ここRoslyn Scripting API を使用して、非常に柔軟なソリューションを提供します。

現時点での正解は、Roslynチームがそれをサポートするためのオープンな提案/問題を抱えているということです。オープンソースに参加している.NetFoundationとMicrosoftのおかげで、この機能の開発についてここで読むことができます。

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

8
Jeremy Thompson

アノテーションを使用するよりも少し汚いですが、プリプロセッサディレクティブを使用してコードをブロックすると、フラグに基づいてコードをコンパイルする方法を構成できます。

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

以下の#ifMyFlagのコードは、MyFlagが定義されている場合にのみコンパイルされます

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

    #endif

    // method-body 
}
3
user942620