Как изменить код перед компиляцией?

Используя Roslyn, я хотел бы изменить свой код С# перед фактической компиляцией. Пока мне просто нужно что-то вроде:

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

И на основе аннотации я хотел бы ввести некоторый код в начале метода и в конце метода.

Я знаю PostSharp, но это не то, что мне хотелось бы.

Можно ли это сделать с Рослином? И если да, не могли бы вы привести мне пример?

Ответ 1

Вот быстрый и грязный способ делать то, что вы хотите. Он основан на одном из приведенных выше замечаний, который указывает на 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 файлы, которые являются частью компиляции.

С помощью файлов С#, переписывание может быть выполнено с помощью 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, вам просто нужно вызвать метод rewriter Visit в корне дерева. Это создаст модифицированное дерево, которое вы можете записать в файл. (Или вы можете изменить исходный файл, либо записать результат на новый, и соответственно изменить файл .RSP.)

Извините за отсутствие полного рабочего решения, но я надеюсь, этого достаточно, чтобы вы начали.

Ответ 2

Как я уже отмечал в своем комментарии, в настоящее время он недоступен. Хотя вы могли бы что-то собрать вместе, используя методы АОП, показанные здесь с API-интерфейсом Roslyn Scripting API, чтобы обеспечить очень гибкое решение.

Правильный ответ на данный момент времени заключается в том, что у команды Roslyn есть открытое предложение/вопрос для его поддержки. Благодаря .Net Foundation и Microsoft, участвующим в Open Source, вы можете прочитать об этом развитии функций здесь:

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

Ответ 3

Это немного грязнее, чем использование аннотации, но блокировка кода с помощью препроцессорных директив позволит вам настроить, как ваш код компилируется на основе флагов.

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 
}