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:)
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());
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.
}
}
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 unBlockSyntax
. -
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.
je fais quelque chose de très similaire, et J'utilise Roslyn pour analyser le code C existant aussi. Cependant, j'utilise pour générer le nouveau code. Les gabarits T4 sont conçus pour la génération de texte, et fournissent une abstraction très agréable de sorte que vous pouvez réellement spécifier des choses qui ressemblent au code au lieu de cet arbre d'objet fou.