Подтвердить что ты не робот

Использование Roslyn для синтаксического анализа/преобразования/генерации кода: я слишком высоко или слишком низко?

(Я пытаюсь решить проблему Application.Settings/MVVM, создав класс интерфейса и оболочки из файла настроек, созданного vs. )

Мне бы хотелось:

  • Разберите объявление класса из файла
  • Создайте объявление интерфейса, основанное только на (нестационарных) свойствах класса
  • Создайте класс-оболочку, который реализует этот интерфейс, берет экземпляр исходного класса в конструкторе и "передает" все свойства в экземпляр.
  • Создайте другой класс, который реализует интерфейс напрямую.

Мой вопрос в два раза:

  • Я лаяю неправильное дерево? Будет ли я лучше использовать Code-Dom, T4, Regex (!) Для этого или часть этого? (Я не против немного дополнительной работы, поскольку это в основном опыт обучения.)
  • Если Рослин - это способ пойти, на какой части я должен смотреть? Я наивно надеялся, что будет какой-то способ ходить по дереву и выплескивать только те куски, которые я хочу, но у меня возникают проблемы с тем, чтобы использовать/использовать SyntaxRewriter для этого или используйте конструкцию в свободном стиле, запрашивая источник несколько раз для необходимых мне битов.

Если вы хотите прокомментировать аспект MVVM, вы можете, но это не главная проблема вопроса:)

4b9b3361

Ответ 1

Если ваше требование - синтаксический анализ исходного кода на С#, то я думаю, что Roslyn - хороший выбор. И если вы собираетесь использовать его для этой части, я думаю, что имеет смысл использовать ее для генерации кода.

Генерация кода с использованием Roslyn может быть довольно многословной (особенно по сравнению с CodeDom), но я думаю, что это не будет большой проблемой для вас.

Я думаю, что SyntaxRewriter лучше всего подходит для локализованных изменений кода. Но вы спрашиваете о разборе всего класса и создании типов на основе этого, я думаю, для этого, запрос дерева синтаксиса будет работать лучше всего.

Например, самый простой пример создания интерфейса только для чтения для всех свойств в классе может выглядеть примерно так:

var originalClass =
    compilationUnit.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
string originalClassName = originalClass.Identifier.ValueText;
var properties =
    originalClass.DescendantNodes().OfType<PropertyDeclarationSyntax>();

var generatedInterface =
    SyntaxFactory.InterfaceDeclaration('I' + originalClassName)
          .AddMembers(
              properties.Select(
                  p =>
                  SyntaxFactory.PropertyDeclaration(p.Type, p.Identifier)
                        .AddAccessorListAccessors(
                            SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                                  .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))))
                        .ToArray());

Ответ 2

Я думаю, что Roslyn - отличный способ решить эту проблему. В терминах какой части Roslyn я бы использовал - я бы, вероятно, использовал SyntaxWalker над исходным классом, а затем использовал Fluent API для создания нового SyntaxNodes для новых типов, которые вы хотите сгенерировать. Возможно, вы сможете повторно использовать некоторые части исходного дерева в сгенерированном коде (например, списки аргументов и т.д.).

Быстрый пример того, как это может выглядеть:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
using Roslyn.Services.CSharp;

    class Program
    {
        static void Main(string[] args)
        {
            var syntaxTree = SyntaxTree.ParseText(@"
class C
{
    internal void M(string s, int i)
    {
    }
}");


        }
    }


class Walker : SyntaxWalker
{
    private InterfaceDeclarationSyntax @interface = Syntax.InterfaceDeclaration("ISettings");

    private ClassDeclarationSyntax wrapperClass = Syntax.ClassDeclaration("SettingsWrapper")
        .WithBaseList(Syntax.BaseList(
            Syntax.SeparatedList<TypeSyntax>(Syntax.ParseTypeName("ISettings"))));

    private ClassDeclarationSyntax @class = Syntax.ClassDeclaration("SettingsClass")
        .WithBaseList(Syntax.BaseList(
            Syntax.SeparatedList<TypeSyntax>(Syntax.ParseTypeName("ISettings"))));

    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        var parameters = node.ParameterList.Parameters.ToArray();
        var typeParameters = node.TypeParameterList.Parameters.ToArray();
        @interface = @interface.AddMembers(
            Syntax.MethodDeclaration(node.ReturnType, node.Identifier.ToString())
                .AddParameterListParameters(parameters)
                .AddTypeParameterListParameters(typeParameters));

        // More code to add members to the classes too.
    }
}

Ответ 3

Я делаю что-то очень похожее, и я использую Roslyn для анализа существующего кода С#. Тем не менее, я использую T4 templates для генерации нового кода. Шаблоны T4 предназначены для генерации текста и обеспечивают очень приятную абстракцию, чтобы вы могли фактически указать материал, который СМОТРЕТЬ как код вместо этого сумасшедшего дерева объектов.

Ответ 4

В вопросе генерации кода мой совет состоит в том, чтобы фактически использовать комбинацию встроенных фрагментов кода (проанализировано с использованием CSharpSyntaxTree.ParseText) и вручную сгенерировано SyntaxNodes, но с сильным предпочтением первому. Я также использовал T4 в прошлом, но я отхожу от них из-за общего отсутствия интеграции и возможностей.

Преимущества/недостатки каждого:

Roslyn ParseText

  • Создает, возможно, более читаемый код-генератор кода.
  • Позволяет использовать метод "шаблонирования текста", например. используя интерполяцию строк С# 6.
  • Менее подробный.
  • Гарантирует действительные деревья синтаксиса.
  • Может быть более результативным.
  • Легче начать работу.
  • Текст может стать более трудным для чтения, чем SyntaxNodes, если большинство является процедурным.

Здание Roslyn SyntaxNode

  • Лучше для преобразования существующих синтаксических деревьев - не нужно начинать с нуля.
    • Но существующие мелочи могут сделать это запутанным/сложным.
  • Более подробный. По-видимому, труднее читать и строить.
    • Синтаксические деревья часто сложнее, чем вы думаете
  • SyntaxFactory API предоставляет руководство по допустимому синтаксису.
  • Roslyn Quoter помогает преобразовать текстовый код в код factory.
  • Синтаксические деревья не обязательно действительны.
  • Код, возможно, более прочный после его написания.

Шаблоны T4

  • Хорошо, если большинство генерируемых кодов - это плита котла.
  • Нет надлежащей поддержки CI.
  • Отсутствие подсветки синтаксиса или intellisense без сторонних расширений.
  • Индивидуальное сопоставление между входными и выходными файлами.
    • Не идеально, если вы делаете более сложное поколение, например. целая иерархия классов, основанная на одном входе.
  • По-прежнему возможно использовать Roslyn для "отражения" типов ввода, иначе вы столкнетесь с проблемами с System.Reflection и файловыми замками и т.д.
  • Меньший доступный API. T4 включает в себя, параметры и т.д., Может ввести в заблуждение.

Советы по генерации кода Roslyn

  • Если вы только разбираете фрагменты кода, например., вам нужно будет использовать CSharpParseOptions.Default.WithKind(SourceCodeKind.Script), чтобы вернуть верные узлы синтаксиса.
  • Если вы разборете весь блок кода для тела метода, тогда вам нужно проанализировать его как GlobalStatementSyntax, а затем получить доступ к свойству Statement как BlockSyntax.
  • Используйте вспомогательный метод для синтаксического анализа одиночного SyntaxNodes:

        private static TSyntax ParseText<TSyntax>(string code, bool asScript = false)
        {
            var options = asScript
                ? CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)
                : CSharpParseOptions.Default;
    
            var syntaxNodes =
                CSharpSyntaxTree.ParseText(code, options)
                    .GetRoot()
                    .ChildNodes();
    
            return syntaxNodes.OfType<TSyntax>().First();
        }
    
  • При создании SyntaxNodes вручную вам обычно нужно сделать окончательный вызов SyntaxTree.NormalizeWhitespace(elasticTrivia: true), чтобы сделать код "round-trippable".
  • Обычно вы хотите использовать SyntaxNode.ToFullString(), чтобы получить фактический текст кода, включая мелочи.
  • Используйте SyntaxTree.WithFilePath() как удобное место для хранения конечного имени файла, когда вы приходите, чтобы выписать код.
  • Если ваша цель состоит в том, чтобы выводить исходные файлы, конечная игра заканчивается действительным CompilationUnitSyntaxs.
  • Не забывайте красиво печатать, используя Formatter.Format как один из заключительных шагов.