c# - permitir - Uso de Roslyn para analizar/transformar/generar código: ¿estoy apuntando demasiado alto o demasiado bajo?
poner acentos en c# (4)
Creo que Roslyn es una excelente manera de resolver este problema. En cuanto a la parte de Roslyn que usaría, probablemente usaría un SyntaxWalker
sobre la clase original y luego usaría la API de SyntaxNodes
para crear nuevos SyntaxNodes
para los nuevos tipos que desea generar. Es posible que pueda reutilizar algunas partes del árbol original en el código generado (por ejemplo, las listas de argumentos, etc.).
Un ejemplo rápido de cómo podría verse esto es:
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.
}
}
(Lo que estoy tratando de hacer es Application.Settings/MVVM problema Application.Settings/MVVM generando una interfaz y una clase de envoltorio a partir del archivo de configuración generado por vs).
Lo que me gustaría hacer es:
- Analizar una declaración de clase del archivo
- Genere una declaración de interfaz basada solo en las propiedades (no estáticas) de la clase
- Genere una clase envoltura que implementa esta interfaz, toma una instancia de la clase original en el constructor y "canaliza" todas las propiedades hasta la instancia.
- Genera otra clase que implemente la interfaz directamente.
Mi pregunta es doble:
- ¿Estoy ladrando el árbol equivocado? ¿Estaría mejor usando Code-Dom, T4, Regex (!) Para esto, o parte de esto? (No me importa un poco de trabajo extra, ya que esto es principalmente una experiencia de aprendizaje).
- Si Roslyn es el camino a seguir, ¿qué parte debería estar mirando? Tenía la ingenua esperanza de que hubiera alguna forma de caminar por el árbol y escupir solo los pedacitos que quiero, pero tengo problemas para entender si debo usar SyntaxRewriter o cómo hacerlo. use una construcción de estilo fluido, consultando la fuente varias veces para los bits que necesito.
Si quieres comentar sobre el aspecto de MVVM, puedes hacerlo, pero esa no es la idea principal de la pregunta :)
Estoy haciendo algo muy similar, y estoy usando a Roslyn para analizar también el código C # existente. Sin embargo, estoy usando plantillas T4 para generar el nuevo código. Las plantillas T4 están diseñadas para la generación de texto, y proporcionan una abstracción muy agradable para que puedas especificar cosas que parezcan código, en lugar de este árbol de objetos locos.
Si su requisito es analizar el código fuente de C #, creo que Roslyn es una buena opción. Y si lo va a utilizar para esta parte, creo que también tiene sentido usarlo para las generaciones de código.
La generación de código usando Roslyn puede ser bastante detallada (especialmente cuando se compara con CodeDom), pero creo que no será un gran problema para ti.
Creo que SyntaxRewriter
es el más adecuado para realizar cambios localizados en el código. Pero está preguntando acerca de analizar toda la clase y generar tipos basados en eso, creo que para eso, consultar el árbol de sintaxis directamente funcionaría mejor.
Por ejemplo, el ejemplo más simple de generar una interfaz de solo lectura para todas las propiedades de una clase podría tener este aspecto:
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());
Sobre la cuestión de la generación de código, mi consejo es usar una combinación de fragmentos de código en línea (analizados usando CSharpSyntaxTree.ParseText
) y SyntaxNodes
generados manualmente, pero con una fuerte preferencia por el primero. También he usado T4 en el pasado, pero me estoy alejando de ellos debido a la falta general de integración y capacidad.
Ventajas / desventajas de cada uno:
Roslyn ParseText
- Genera un código generador de código más legible.
- Permite el enfoque de ''plantillas de texto'', por ejemplo, mediante la interpolación de cadenas C # 6.
- Menos verboso.
- Garantiza árboles de sintaxis válidos.
- Puede ser más performante .
- Más fácil de empezar.
- El texto puede ser más difícil de leer que los
SyntaxNodes
si la mayoría es de procedimiento.
Edificio Roslyn SyntaxNode
- Mejor para transformar los árboles de sintaxis existentes, no es necesario comenzar desde cero.
- Pero las trivialidades existentes pueden hacer esto confuso / complejo.
- Más detallado. Podría decirse que es más difícil de leer y construir.
- Los árboles de sintaxis son a menudo más complejos de lo que imaginas
-
SyntaxFactory
API proporciona orientación sobre la sintaxis válida. - Roslyn Quoter le ayuda a transformar el código textual en el código de fábrica.
- Los árboles de sintaxis no son necesariamente válidos.
- El código es quizás más robusto una vez escrito.
Plantillas t4
- Bueno si la mayoría del código a generar es placa de caldera.
- No hay soporte adecuado de CI.
- Sin resaltado de sintaxis o inteligencia sin extensiones de terceros.
- Mapeo uno a uno entre los archivos de entrada y salida.
- No es ideal si está haciendo una generación más compleja, por ejemplo, una jerarquía de clases completa basada en una sola entrada.
- Probablemente quiera usar Roslyn para "reflexionar" en los tipos de entrada, de lo contrario, tendrá problemas con el sistema. Reflexión y bloqueos de archivos, etc.
- API menos descubrible. T4 incluye, parámetros, etc. puede ser confuso para aprender.
Consejos de Roslyn Code-Gen
- Si solo está analizando fragmentos de código, por ejemplo, declaraciones de métodos, entonces necesitará usar
CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)
para recuperar los nodos de sintaxis correctos. - Si está analizando un bloque de código completo para un cuerpo de método, querrá analizarlo como
GlobalStatementSyntax
y luego acceder a la propiedadStatement
comoBlockSyntax
. Utilice un método auxiliar para analizar los
SyntaxNodes
individuales: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(); }
- Cuando
SyntaxNodes
a mano, normalmente querrá hacer una llamada final aSyntaxTree.NormalizeWhitespace(elasticTrivia: true)
para hacer que el código sea "redondeable". - Por lo general, deseará usar
SyntaxNode.ToFullString()
para obtener el texto del código real, incluida la trivia. - Use
SyntaxTree.WithFilePath()
como un lugar conveniente para almacenar el nombre del archivo final para cuando escriba el código. - Si su objetivo es generar archivos de origen, el juego final es terminar con
CompilationUnitSyntaxs
válidos. - No se olvide de realizar una impresión bonita con
Formatter.Format
como uno de los pasos finales.