Utiliser Roslyn pour analyser/transformer / générer du code: est-ce que je vise trop haut ou trop bas?

(ce que j'essaie de faire est de travailler autour de l'Application .Settings / MVVM problème en générant une interface et une classe wrapper à partir du fichier de paramètres généré par vs.)

ce que j'aimerais faire est:

  • Analyser une déclaration de classe à partir d'un fichier
  • générer une déclaration d'interface basée uniquement sur les propriétés (non statiques) de la classe
  • Générer une classe wrapper qui implémente cette interface, prend une instance de la classe d'origine dans le constructeur, et "pipes" toutes les propriétés jusqu'à l'instance.
  • génère une autre classe qui implémente l'interface directement.

ma question Est double:

  • est-ce que j'aboie sur le mauvais arbre? Serait-il préférable que J'utilise Code-Dom, T4, Regex(!) pour cela, ou une partie de cette? (Je n'ai pas l'esprit d'un peu de travail supplémentaire, comme c'est généralement une expérience d'apprentissage.)
  • si Roslyn est la voie à suivre, quelle partie devrais-je regarder? J'espérais naïvement qu'il y aurait un moyen de marcher dans l'arbre et de cracher juste les morceaux que je veux, mais j'ai du mal à me faire une idée si/comment utiliser le SyntaxRewriter pour le faire, ou s'il faut utiliser une construction de style fluent, en questionnant la source plusieurs fois pour les morceaux dont j'ai besoin.

si vous voulez commenter l'aspect MVVM vous pouvez, mais ce n'est pas l'idée maîtresse de la question:)

21
demandé sur Community 2013-05-02 16:30:49

4 réponses

si votre besoin est d'analyser le code source C#, alors je pense que Roslyn est un bon choix. Et si vous allez l'utiliser pour cette partie, je pense qu'il est également judicieux de l'utiliser pour le code générations.

la génération de Code en utilisant Roslyn peut être très verbeuse (surtout par rapport au CodeDom), mais je pense que ce ne sera pas un gros problème pour vous.

je pense que SyntaxRewriter est le mieux adapté pour faire des changements localisés dans le code. Mais vous vous demandez A propos de l'analyse de classe entière et de la génération de types basés sur cela, je pense que pour cela, interroger directement l'arbre de syntaxe fonctionnerait le mieux.

par exemple, l'exemple le plus simple de générer une interface en lecture seule pour toutes les propriétés d'une classe pourrait ressembler à quelque chose comme ceci:

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());
12
répondu svick 2015-10-05 21:52:18

je pense que Roslyn est un excellent moyen de résoudre ce problème. Pour ce qui est de la partie de Roslyn que j'utiliserais - j'utiliserais probablement un SyntaxWalker par rapport à la classe d'origine, puis j'utiliserais L'API Fluent pour créer un nouveau SyntaxNodes pour les nouveaux types que vous voulez générer. Vous pourriez être en mesure de réutiliser certaines parties de l'arbre d'origine dans le code généré (par exemple, l'argument des listes, etc).

un exemple rapide de ce à quoi cela pourrait ressembler est:

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.
    }
}
5
répondu Kevin Pilch 2013-05-02 15:10:31

sur la question de la génération de code, mon conseil est d'utiliser effectivement une combinaison de snippets de code en ligne (parsed en utilisant CSharpSyntaxTree.ParseText ) et générée manuellement SyntaxNodes , mais avec une forte préférence pour le premier. J'ai également utilisé le T4 dans le passé, mais je m'en éloigne en raison d'un manque général d'intégration et de capacité.

avantages / inconvénients de chacun:

Roslyn ParseText

  • Génère un code de générateur de code plus lisible.
  • permet l'approche "text templating" par exemple en utilisant l'interpolation de chaîne C# 6.
  • moins verbeux.
  • garantit des arbres de syntaxe valides.
  • peut être plus performant .
  • plus facile à démarrer.
  • peut devenir plus difficile à lire que SyntaxNodes si la majorité est procédurale.

Roslyn SyntaxNode building

  • mieux pour transformer les arbres de syntaxe existants - pas besoin de recommencer à zéro.
    • mais les anecdotes existantes peuvent rendre cela confus/complexe.
  • plus verbeux. Plus difficile à lire et à construire.
    • les arbres de syntaxe sont souvent plus complexes que vous ne l'imaginez""
  • L'API
  • SyntaxFactory fournit des directives sur la syntaxe valide.
  • Roslyn Quoter vous aide à transformer le code textuel en code d'usine.
  • Les arbres de syntaxe
  • ne sont pas nécessairement valides.
  • Le Code
  • est peut-être plus robuste une fois écrit.

modèles T4

  • Bon si la majorité de code généré est de la chaudière de la plaque.
  • pas de support de CI approprié.
  • pas de mise en évidence de syntaxe ou d'intellisense sans extensions de tiers.
  • mise en correspondance de un à un entre les fichiers d'entrée et de sortie.
    • pas idéal si vous faites une génération plus complexe, par exemple une hiérarchie de classe entière basée sur une seule entrée.
  • veut toujours probablement utiliser Roslyn pour "réfléchir" sur les types d'entrée, sinon vous avoir des ennuis avec le Système.Réflexion, serrures, etc.
  • moins d'API à découvrir. T4 inclut, les paramètres etc. peut être source de confusion pour apprendre.

Roslyn code-gen tips

  • si vous analysez uniquement des extraits de code, par exemple des énoncés de méthode, vous devrez utiliser CSharpParseOptions.Default.WithKind(SourceCodeKind.Script) pour récupérer les noeuds de syntaxe appropriés.
  • si vous analysez un bloc entier de code pour une méthode corps ensuite, vous voudrez l'analyser comme un GlobalStatementSyntax et puis accéder à la propriété Statement comme un BlockSyntax .
  • utiliser une méthode d'aide à l'analyse simple 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();
        }
    
  • lorsque vous construisez SyntaxNodes à la main, vous voudrez généralement faire un dernier appel à SyntaxTree.NormalizeWhitespace(elasticTrivia: true) pour rendre le code"round-trippable".
  • typiquement, vous voudrez utiliser SyntaxNode.ToFullString() pour obtenir le réel texte codé incluant des anecdotes.
  • utilisez SyntaxTree.WithFilePath() comme un endroit commode pour stocker le nom de fichier éventuel pour quand vous venez pour écrire le code.
  • si votre but est de produire des fichiers source, la fin du jeu est de finir avec CompilationUnitSyntaxs valide .
  • N'oubliez pas de bien imprimer en utilisant Formatter.Format comme une des étapes finales.
5
répondu Schneider 2016-05-23 06:52:40