Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tutorial, explorará la API de sintaxis. La API de sintaxis proporciona acceso a las estructuras de datos que describen un programa de C# o Visual Basic. Estas estructuras de datos tienen suficientes detalles que pueden representar completamente cualquier programa de cualquier tamaño. Estas estructuras pueden describir programas completos que se compilan y ejecutan correctamente. También pueden describir programas incompletos, al escribirlos, en el editor.
Para habilitar esta expresión enriquecida, las estructuras de datos y las API que componen la API de sintaxis son necesariamente complejas. Comencemos con el aspecto de la estructura de datos para el programa típico "Hola mundo":
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Examine el texto del programa anterior. Reconoce elementos conocidos. El texto completo representa un único archivo de origen o una unidad de compilación. Las tres primeras líneas de ese archivo de origen son directivas de uso. El origen restante se encuentra en una declaración de espacio de nombres. La declaración de espacio de nombres contiene una declaración de clase secundaria. La declaración de clase contiene una declaración de método.
La API de sintaxis crea una estructura de árbol con la raíz que representa la unidad de compilación. Los nodos del árbol representan las directivas, la using declaración de espacio de nombres y todos los demás elementos del programa. La estructura de árbol continúa hasta los niveles más bajos: la cadena "Hello World!" es un token literal de cadena que es descendiente de un argumento. La API de sintaxis proporciona acceso a la estructura del programa. Puede consultar prácticas de código específicas, recorrer todo el árbol para comprender el código y crear árboles nuevos modificando el árbol existente.
Esta breve descripción proporciona información general sobre el tipo de información accesible mediante la API de sintaxis. La API de sintaxis no es más que una API formal que describe las construcciones de código conocidas que conoce de C#. Las funcionalidades completas incluyen información sobre cómo se da formato al código, incluidos saltos de línea, espacios en blanco y sangría. Con esta información, puede representar completamente el código tal y como lo escriben los programadores humanos o el compilador. El uso de esta estructura le permite interactuar con el código fuente en un nivel profundamente significativo. Ya no es cadenas de texto, pero los datos que representan la estructura de un programa de C#.
Para empezar, deberá instalar el SDK de .NET Compiler Platform:
Instrucciones de instalación: Instalador de Visual Studio
Hay dos maneras diferentes de encontrar el SDK de .NET Compiler Platform en el Instalador de Visual Studio:
Instalación mediante el Instalador de Visual Studio: vista Cargas de trabajo
El SDK de .NET Compiler Platform no se selecciona automáticamente como parte de la carga de trabajo de desarrollo de extensiones de Visual Studio. Deba seleccionarlo como un componente opcional.
- Ejecución del instalador de Visual Studio
- Seleccione Modificar.
- Active la carga de trabajo Desarrollo de extensiones de Visual Studio.
- Abra el nodo desarrollo de extensiones de Visual Studio en el árbol de resumen.
- Asegúrese de que la casilla del SDK de la plataforma del compilador de .NET esté activada.
- Seleccione Modificar.
Opcionalmente, también querrá que el editor DGML muestre gráficos en el visualizador:
- Abra el nodo Componentes individuales en el árbol de resumen.
- Active la casilla para el editor DGML.
Instalación mediante la pestaña Instalador de Visual Studio: componentes individuales
- Ejecución del instalador de Visual Studio
- Seleccione Modificar.
- Seleccione la pestaña Componentes individuales.
- Marque la casilla para .NET Compiler Platform SDK. Lo encontrará en la parte superior de la sección Compiladores, herramientas de compilación y entornos de ejecución .
- Seleccione Modificar.
Opcionalmente, también querrá que el editor DGML muestre gráficos en el visualizador:
- Active la casilla Editor de DGML. La encontrará en la sección Herramientas de código .
Descripción de los árboles de sintaxis
Use la API de sintaxis para cualquier análisis de la estructura del código de C#. La API de sintaxis expone los analizadores, los árboles de sintaxis y las utilidades para analizar y construir árboles de sintaxis. Es cómo buscar código para elementos de sintaxis específicos o leer el código de un programa.
Un árbol de sintaxis es una estructura de datos que usan los compiladores de C# y Visual Basic para comprender los programas de C# y Visual Basic. Los árboles de sintaxis se generan mediante el mismo analizador que se ejecuta cuando se compila un proyecto o un desarrollador alcanza F5. Los árboles de sintaxis tienen plena fidelidad con el lenguaje; cada bit de información de un archivo de código se representa en el árbol. Al escribir un árbol de sintaxis en el texto se reproduce el texto original exacto que se ha analizado. Los árboles de sintaxis también son inmutables; una vez creado un árbol de sintaxis nunca se puede cambiar. Los consumidores de los árboles pueden analizar los árboles en múltiples hilos, sin bloqueos ni otras medidas de simultaneidad, sabiendo que los datos nunca cambian. Puede usar las API para crear árboles que sean el resultado de modificar un árbol existente.
Los cuatro bloques de creación principales de árboles de sintaxis son:
- La Microsoft.CodeAnalysis.SyntaxTree clase, de la cual una instancia representa un árbol de análisis completo. SyntaxTree es una clase abstracta que tiene derivados específicos del lenguaje. Use los métodos de análisis de la Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree clase (o Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree) para analizar texto en C# (o Visual Basic).
- Clase Microsoft.CodeAnalysis.SyntaxNode , instancias de las que representan construcciones sintácticas, como declaraciones, instrucciones, cláusulas y expresiones.
- Estructura Microsoft.CodeAnalysis.SyntaxToken , que representa una palabra clave, un identificador, un operador o una puntuación individuales.
- Por último, la Microsoft.CodeAnalysis.SyntaxTrivia estructura, que representa bits de información sintácticamente insignificantes, como el espacio en blanco entre tokens, directivas de preprocesamiento y comentarios.
Trivia, tokens y nodos se componen jerárquicamente para formar un árbol que representa completamente todo en un fragmento de código de Visual Basic o C#. Puede ver esta estructura mediante la ventana Visualizador de sintaxis . En Visual Studio, elija Ver>Otras ventanas>Visualizador de sintaxis. Por ejemplo, el archivo de origen de C# anterior examinado mediante el visualizador de sintaxis tiene el siguiente aspecto:
SyntaxNode: Azul | SintaxisToken: Verde | SyntaxTrivia:
rojo
Si navega por esta estructura de árbol, puede encontrar cualquier instrucción, expresión, token o bit de espacio en blanco en un archivo de código.
Aunque puede encontrar cualquier cosa en un archivo de código mediante las API de sintaxis, la mayoría de los escenarios implican examinar fragmentos pequeños de código o buscar instrucciones o fragmentos concretos. Los dos ejemplos siguientes muestran usos típicos para examinar la estructura del código o buscar instrucciones únicas.
Recorrido de árboles
Puede examinar los nodos de un árbol de sintaxis de dos maneras. Puede recorrer el árbol para examinar cada nodo o puede consultar elementos o nodos específicos.
Recorrido manual
Puede ver el código terminado de este ejemplo en nuestro repositorio de GitHub.
Nota:
Los tipos de árbol de sintaxis usan herencia para describir los distintos elementos de sintaxis que son válidos en diferentes ubicaciones del programa. El uso de estas API suele significar convertir propiedades o miembros de colección en tipos derivados específicos. En los ejemplos siguientes, la asignación y las conversiones son instrucciones independientes, usando variables con tipo explícito. Puede leer el código para ver los tipos de devolución de la API y el tipo en tiempo de ejecución de los objetos devueltos. En la práctica, es más común usar variables con tipo implícito y confiar en nombres de API para describir el tipo de objetos que se examinan.
Cree un nuevo proyecto de C# para una herramienta de análisis de código independiente.
- En Visual Studio, elija Archivo>nuevo>proyecto para mostrar el cuadro de diálogo Nuevo proyecto.
- En Visual C#>Extensibilidad, seleccione Herramienta autónoma de análisis de código.
- Asigne al proyecto el nombre "SyntaxTreeManualTraversal" y haga clic en Aceptar.
Va a analizar el programa básico "Hola mundo!" que se mostró anteriormente.
Agregue el texto del programa Hello World como una constante en su Program clase:
const string programText =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
A continuación, agregue el código siguiente para compilar el árbol de sintaxis para el texto del código en la programText constante. Agregue la siguiente línea al Main método :
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Esas dos líneas crean el árbol y recuperan el nodo raíz de ese árbol. Ahora puede examinar los nodos del árbol. Agregue estas líneas al Main método para mostrar algunas de las propiedades del nodo raíz en el árbol:
WriteLine($"The tree is a {root.Kind()} node.");
WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using directives. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
WriteLine($"\t{element.Name}");
Ejecute la aplicación para ver lo que el código ha descubierto sobre el nodo raíz de este árbol.
Normalmente, recorrerías el árbol para comprender el código. En este ejemplo, va a analizar el código que ya conoce para explorar las API. Agregue el código siguiente para examinar el primer miembro del root nodo:
MemberDeclarationSyntax firstMember = root.Members[0];
WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;
Ese miembro es un Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax. Representa todo en el ámbito de la namespace HelloWorld declaración. Agregue el código siguiente para examinar qué nodos se declaran dentro del HelloWorld espacio de nombres:
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");
Ejecute el programa para ver lo que ha aprendido.
Ahora que sabe que la declaración es un Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, declare una nueva variable de ese tipo para examinar la declaración de la clase. Esta clase solo contiene un miembro: el Main método . Agregue el siguiente código para encontrar el método Main y convertirlo en Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax.
var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
WriteLine($"There are {programDeclaration.Members.Count} members declared in the {programDeclaration.Identifier} class.");
WriteLine($"The first member is a {programDeclaration.Members[0].Kind()}.");
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];
El nodo de declaración de método contiene toda la información sintáctica sobre el método . Vamos a mostrar el tipo de valor devuelto del Main método, el número y los tipos de los argumentos y el texto del cuerpo del método. Agregue el código siguiente:
WriteLine($"The return type of the {mainDeclaration.Identifier} method is {mainDeclaration.ReturnType}.");
WriteLine($"The method has {mainDeclaration.ParameterList.Parameters.Count} parameters.");
foreach (ParameterSyntax item in mainDeclaration.ParameterList.Parameters)
WriteLine($"The type of the {item.Identifier} parameter is {item.Type}.");
WriteLine($"The body text of the {mainDeclaration.Identifier} method follows:");
WriteLine(mainDeclaration.Body?.ToFullString());
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
Ejecute el programa para ver toda la información que ha descubierto sobre este programa:
The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using directives. They are:
System
System.Collections
System.Linq
System.Text
The first member is a NamespaceDeclaration.
There are 1 members declared in this namespace.
The first member is a ClassDeclaration.
There are 1 members declared in the Program class.
The first member is a MethodDeclaration.
The return type of the Main method is void.
The method has 1 parameters.
The type of the args parameter is string[].
The body text of the Main method follows:
{
Console.WriteLine("Hello, World!");
}
Métodos de consulta
Además de recorrer árboles, también puede explorar el árbol sintáctico mediante los métodos de consulta definidos en Microsoft.CodeAnalysis.SyntaxNode. Los métodos deberían resultar inmediatamente familiares para cualquiera que esté familiarizado con XPath. Puede usar estos métodos con LINQ para encontrar rápidamente cosas en un árbol. SyntaxNode tiene métodos de consulta como DescendantNodes, AncestorsAndSelf y ChildNodes.
Puede usar estos métodos de consulta para buscar el argumento al Main método como alternativa a navegar por el árbol. Agregue el código siguiente a la parte inferior del Main método:
var firstParameters = from methodDeclaration in root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
where methodDeclaration.Identifier.ValueText == "Main"
select methodDeclaration.ParameterList.Parameters.First();
var argsParameter2 = firstParameters.Single();
WriteLine(argsParameter == argsParameter2);
La primera instrucción usa una expresión LINQ y el DescendantNodes método para buscar el mismo parámetro que en el ejemplo anterior.
Ejecute el programa y puede ver que la expresión LINQ encontró el mismo parámetro que navegar manualmente por el árbol.
En el ejemplo se usan WriteLine sentencias para mostrar información sobre los árboles de sintaxis conforme se recorren. También puede aprender mucho más ejecutando el programa terminado con el depurador. Puede examinar más de las propiedades y métodos que forman parte del árbol de sintaxis creado para el programa hello world.
Exploradores de sintaxis
A menudo, desea buscar todos los nodos de un tipo específico en un árbol de sintaxis, por ejemplo, cada declaración de propiedad en un archivo. Al extender la Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker clase y invalidar el VisitPropertyDeclaration(PropertyDeclarationSyntax) método, se procesan todas las declaraciones de propiedad en un árbol de sintaxis sin conocer su estructura de antemano. CSharpSyntaxWalker es un tipo específico de CSharpSyntaxVisitor que visita recursivamente un nodo y cada uno de sus elementos secundarios.
En este ejemplo se implementa un CSharpSyntaxWalker que examina un árbol de sintaxis. Recopila directivas using que encuentra que no importan un System espacio de nombres.
Cree un nuevo proyecto de herramienta de análisis de código independiente C#; asígnele el nombre "SyntaxWalker".
Puede ver el código terminado de este ejemplo en nuestro repositorio de GitHub. El ejemplo de GitHub contiene ambos proyectos descritos en este tutorial.
Como en el ejemplo anterior, puede definir una constante de cadena para contener el texto del programa que va a analizar:
const string programText =
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace TopLevel
{
using Microsoft;
using System.ComponentModel;
namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;
class Foo { }
}
namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;
class Bar { }
}
}";
Este texto de origen contiene using directivas dispersas en cuatro ubicaciones diferentes: el nivel de archivo, en el espacio de nombres de nivel superior y en los dos espacios de nombres anidados. En este ejemplo se resalta un escenario básico para usar la clase para consultar el CSharpSyntaxWalker código. Sería complicado visitar todos los nodos del árbol de sintaxis raíz para buscar mediante declaraciones. En su lugar, cree una clase derivada y sobrescriba el método que se llama solo cuando el nodo actual en el árbol es una directiva using. El visitante no realiza ningún trabajo en ningún otro tipo de nodo. Este único método examina cada una de las using directivas y crea una colección de los espacios de nombres que no están en el System espacio de nombres. Se construye un CSharpSyntaxWalker que examina todas las directivas de using, pero solo las de using.
Ahora que ha definido el texto del programa, debe crear un SyntaxTree y obtener la raíz de ese árbol:
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
A continuación, cree una nueva clase. En Visual Studio, elija ProjectAdd New Item (Agregar nuevo elemento> En el cuadro de diálogo Agregar nuevo elemento , escriba UsingCollector.cs como nombre de archivo.
Implemente la using funcionalidad de visitante en la UsingCollector clase . Empiece por hacer que la UsingCollector clase derive de CSharpSyntaxWalker.
class UsingCollector : CSharpSyntaxWalker
Necesita un almacenamiento para manejar los nodos del espacio de nombres que está recopilando. Declare una propiedad pública de solo lectura en la UsingCollector clase ; use esta variable para almacenar los UsingDirectiveSyntax nodos que encuentre:
public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();
La clase base implementa CSharpSyntaxWalker la lógica para visitar cada nodo en el árbol de sintaxis. La clase derivada invalida los métodos llamados para los nodos específicos que le interesan. En este caso, te interesa cualquier using directiva. Esto significa que debe invalidar el VisitUsingDirective(UsingDirectiveSyntax) método . El único argumento para este método es un Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objeto . Esa es una ventaja importante al usar los visitantes: llaman a los métodos sobrescritos con argumentos que ya han sido convertidos al tipo de nodo específico. La Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax clase tiene una Name propiedad que almacena el nombre del espacio de nombres que se va a importar. Es una Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Agregue el código siguiente en la VisitUsingDirective(UsingDirectiveSyntax) sobrescritura:
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
WriteLine($"\tVisitUsingDirective called with {node.Name}.");
if (node.Name.ToString() != "System" &&
!node.Name.ToString().StartsWith("System."))
{
WriteLine($"\t\tSuccess. Adding {node.Name}.");
this.Usings.Add(node);
}
}
Al igual que con el ejemplo anterior, usted ha agregado una variedad de WriteLine instrucciones para ayudar a comprender este método. Puede ver cuando se le llama y qué argumentos se le pasan cada vez.
Por último, debe agregar dos líneas de código para crear UsingCollector y hacer que visite el nodo raíz, recopilando todas las using directivas. A continuación, agregue un bucle foreach para mostrar las directivas using que encontró su recolector:
var collector = new UsingCollector();
collector.Visit(root);
foreach (var directive in collector.Usings)
{
WriteLine(directive.Name);
}
Compile y ejecute el programa. Debería ver los siguientes resultados:
VisitUsingDirective called with System.
VisitUsingDirective called with System.Collections.Generic.
VisitUsingDirective called with System.Linq.
VisitUsingDirective called with System.Text.
VisitUsingDirective called with Microsoft.CodeAnalysis.
Success. Adding Microsoft.CodeAnalysis.
VisitUsingDirective called with Microsoft.CodeAnalysis.CSharp.
Success. Adding Microsoft.CodeAnalysis.CSharp.
VisitUsingDirective called with Microsoft.
Success. Adding Microsoft.
VisitUsingDirective called with System.ComponentModel.
VisitUsingDirective called with Microsoft.Win32.
Success. Adding Microsoft.Win32.
VisitUsingDirective called with System.Runtime.InteropServices.
VisitUsingDirective called with System.CodeDom.
VisitUsingDirective called with Microsoft.CSharp.
Success. Adding Microsoft.CSharp.
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
Press any key to continue . . .
¡Felicidades! Ha usado la API de sintaxis para buscar tipos específicos de directivas y declaraciones en el código fuente de C#.