Share via


Introducción al análisis semántico

En este tutorial, se asume que conoce la API de sintaxis. En el artículo Introducción al análisis de sintaxis se proporciona una introducción suficiente.

En este tutorial, explorará las API de símbolo y enlace. Estas API ofrecen información sobre el significado semántico de un programa. Le permiten formular y responder preguntas sobre los tipos representados por cualquier símbolo en el programa.

Deberá instalar el SDK de .NET Compiler Platform:

Instrucciones de instalación: Instalador de Visual Studio

Hay dos maneras distintas de buscar el SDK de .NET Compiler Platform en el Instalador de Visual Studio:

Instalación con el Instalador de Visual Studio: visualización de 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. Se debe seleccionar como un componente opcional.

  1. Ejecute el Instalador de Visual Studio.
  2. Selección de Modificar
  3. Active la carga de trabajo Desarrollo de extensiones de Visual Studio.
  4. Abra el nodo Desarrollo de extensiones de Visual Studio en el árbol de resumen.
  5. Active la casilla SDK de .NET Compiler Platform. La encontrará en última posición bajo los componentes opcionales.

Opcionalmente, también le interesará que el Editor de DGML muestre los gráficos en el visualizador:

  1. Abra el nodo Componentes individuales en el árbol de resumen.
  2. Active la casilla Editor de DGML.

Instalación con el Instalador de Visual Studio: pestaña Componentes individuales

  1. Ejecute el Instalador de Visual Studio.
  2. Selección de Modificar
  3. Haga clic en la pestaña Componentes individuales.
  4. Active la casilla SDK de .NET Compiler Platform. La encontrará en la parte superior bajo la sección Compiladores, herramientas de compilación y tiempos de ejecución.

Opcionalmente, también le interesará que el Editor de DGML muestre los gráficos en el visualizador:

  1. Active la casilla Editor de DGML. La encontrará en la sección Herramientas de código.

Comprender las compilaciones y los símbolos

Conforme trabaja más con el SDK de .NET Compiler, empieza a familiarizarse con las diferencias entre la API de sintaxis y la API semántica. La API de sintaxis le permite buscar en la estructura de un programa. En cambio, a menudo quiere una información más completa sobre la semántica o el significado de un programa. Aunque un fragmento de código o archivo de código dinámico de Visual Basic o C# se puede analizar sintácticamente de forma aislada, no tiene sentido formular preguntas como "¿cuál es el tipo de esta variable?" de manera unilateral. El significado de un nombre de tipo puede depender de las referencias de ensamblado, las importaciones de espacio de nombres u otros archivos de código. Esas preguntas se responden mediante la API semántica, concretamente la clase Microsoft.CodeAnalysis.Compilation.

Una instancia de Compilation es análoga a un único proyecto, tal como muestra el compilador y representa todo lo necesario para compilar un programa de Visual Basic o C#. La compilación incluye el conjunto de archivos de código fuente que se compilarán, las referencias de ensamblado y las opciones del compilador. Puede analizar el significado del código con toda la demás información en este contexto. Una Compilation permite buscar símbolos (entidades como tipos, espacios de nombres, miembros y variables a los que hacen referencia nombres y otras expresiones). El proceso de asociar los nombres y las expresiones con símbolos se denomina enlace.

Como Microsoft.CodeAnalysis.SyntaxTree, Compilation es una clase abstracta con derivados específicos del lenguaje. Al crear una instancia de la compilación, debe invocar un método de generador en la clase Microsoft.CodeAnalysis.CSharp.CSharpCompilation (o Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Consultar símbolos

En este tutorial, volverá a examinar el programa "Hola mundo". En esta ocasión, consultará los símbolos del programa para comprender qué tipos representan esos símbolos. Consultará los tipos en un espacio de nombres y aprenderá a buscar los métodos disponibles en un tipo.

Puede ver el código terminado de este ejemplo en nuestro repositorio de GitHub.

Nota

Los tipos de árbol de sintaxis usan la herencia para describir los diferentes elementos de sintaxis que son válidos en diferentes ubicaciones del programa. A menudo, usar estas API significa convertir propiedades o miembros de colección en tipos derivados concretos. En los ejemplos siguientes, la asignación y las conversiones son instrucciones independientes, con variables con tipo explícito. Puede leer el código para ver los tipos de valor devuelto de la API y el tipo de motor de ejecución de los objetos devueltos. En la práctica, es más habitual usar variables con tipo implícito y basarse en nombres de API para describir el tipo de los objetos que se examinan.

Cree un proyecto de Stand-Alone Code Analysis Tool (Herramienta de análisis de código independiente) de C#:

  • En Visual Studio, elija Archivo>Nuevo>Proyecto para mostrar el cuadro de diálogo Nuevo proyecto.
  • En Visual C#>Extensibilidad, elija Stand-Alone Code Analysis Tool (Herramienta de análisis de código independiente).
  • Asigne al proyecto el nombre "SemanticQuickStart" y haga clic en Aceptar.

Va a analizar el programa básico "Hola mundo!" mostrado anteriormente. Agregue el texto para el programa Hola mundo como una constante en su clase Program:

        const string programText =
@"using System;
using System.Collections.Generic;
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 crear el árbol de sintaxis para el texto del código de la constante programText. Agregue la línea siguiente al método Main:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

A continuación, compile una CSharpCompilation del árbol que ya ha creado. El ejemplo "Hola mundo" se basa en los tipos String y Console. Debe hacer referencia al ensamblado que declara esos dos tipos en la compilación. Agregue la siguiente línea a su método Main para crear una compilación de su árbol de sintaxis, incluida la referencia al ensamblado adecuado:

var compilation = CSharpCompilation.Create("HelloWorld")
    .AddReferences(MetadataReference.CreateFromFile(
        typeof(string).Assembly.Location))
    .AddSyntaxTrees(tree);

El método CSharpCompilation.AddReferences agrega referencias a la compilación. El método MetadataReference.CreateFromFile carga un ensamblado como referencia.

Consultar el modelo semántico

Una vez que tenga una Compilation, puede pedirle un SemanticModel para cualquier SyntaxTree incluido en esa Compilation. Puede considerar el modelo semántico como el origen de toda la información que normalmente obtendría de IntelliSense. Un SemanticModel puede responder a preguntas como "¿Qué nombres entran en el ámbito de esta ubicación?", "¿A qué miembros se puede acceder desde este método?", "¿Qué variables se usan en este bloque de texto?" y "¿A qué hace referencia este nombre o expresión?". Agregue esta instrucción para crear el modelo semántico:

SemanticModel model = compilation.GetSemanticModel(tree);

Enlazar un nombre

La Compilation crea el SemanticModel desde el SyntaxTree. Después de crear el modelo, puede consultarlo para buscar la primera directiva using y recuperar la información de símbolo del espacio de nombres System. Agregue estas dos líneas en su método Main para crear el modelo semántico y recuperar el símbolo de la primera instrucción using:

// Use the syntax tree to find "using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;

// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);

En el código anterior se muestra cómo enlazar el nombre de la primera directiva de using para recuperar un Microsoft.CodeAnalysis.SymbolInfo para el espacio de nombres System. El código anterior también muestra que usa el modelo de sintaxis para buscar la estructura del código y usa el modelo semántico para entender su significado. El modelo de sintaxis busca la cadena System en la instrucción using. El modelo semántico tiene toda la información sobre los tipos definidos en el espacio de nombres System.

Desde el objeto SymbolInfo puede obtener el Microsoft.CodeAnalysis.ISymbol mediante la propiedad SymbolInfo.Symbol. Esta propiedad devuelve el símbolo al que hace referencia esta expresión. Para las expresiones que no hacen referencia a ningún elemento (por ejemplo, los literales numéricos), esta propiedad es null. Cuando el SymbolInfo.Symbol no es NULL, el ISymbol.Kind denota el tipo del símbolo. En este ejemplo, la propiedad ISymbol.Kind es un SymbolKind.Namespace. Agregue el código siguiente al método Main. Recupera el símbolo del espacio de nombres System y, después, muestra todos los espacios de nombres secundarios que se declaran en el espacio de nombres System:

var systemSymbol = (INamespaceSymbol?)nameInfo.Symbol;
if (systemSymbol?.GetNamespaceMembers() is not null)
{
    foreach (INamespaceSymbol ns in systemSymbol?.GetNamespaceMembers()!)
    {
        Console.WriteLine(ns);
    }
}

Ejecute el programa y debería ver la siguiente salida:

System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .

Nota

La salida no incluye todos los espacios de nombres que son secundarios del espacio de nombres System. Muestra cada espacio de nombres que se encuentra en esta compilación, que solo hace referencia al ensamblado donde se declara System.String. Esta compilación no conoce ningún espacio de nombres declarado en otros ensamblados.

Enlazar una expresión

El código anterior muestra cómo buscar un símbolo al enlazarlo a un nombre. Hay otras expresiones en un programa de C# que se pueden enlazar que no son nombres. Para demostrar esta funcionalidad, accederemos al enlace a un literal de cadena sencillo.

El programa "Hola mundo" contiene una Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax, la cadena "Hola, ¡mundo!" que se muestra en la consola.

Para encontrar la cadena "Hola, ¡mundo!", busque el literal de cadena único en el programa. A continuación, una vez que haya encontrado el nodo de sintaxis, obtenga la información de tipo de ese nodo del modelo semántico. Agregue el código siguiente al método Main:

// Use the syntax model to find the literal string:
LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();

// Use the semantic model for type information:
TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

La estructura Microsoft.CodeAnalysis.TypeInfo incluye una propiedad TypeInfo.Type que permite el acceso a la información semántica sobre el tipo del literal. En este ejemplo, es el tipo string. Agregue una declaración que asigne esta propiedad a una variable local:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Para finalizar este tutorial, crearemos una consulta LINQ que crea una secuencia de todos los métodos públicos declarados en el tipo string que devuelven una string. Esta consulta es más compleja, así que la compilaremos línea a línea y, después, la volveremos a construir como una única consulta. El origen de esta consulta es la secuencia de todos los miembros declarados en el tipo string:

var allMembers = stringTypeSymbol?.GetMembers();

Esa secuencia de origen contiene todos los miembros, incluidas las propiedades y los campos, de modo que tiene que filtrarlos con el método ImmutableArray<T>.OfType para buscar los elementos que son objetos Microsoft.CodeAnalysis.IMethodSymbol:

var methods = allMembers?.OfType<IMethodSymbol>();

A continuación, agregue otro filtro para devolver solo aquellos métodos que son públicos y devuelven una string:

var publicStringReturningMethods = methods?
    .Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType, stringTypeSymbol) &&
    m.DeclaredAccessibility == Accessibility.Public);

Seleccione solo la propiedad name y solo los nombres distintos; para ello, elimine las sobrecargas:

var distinctMethods = publicStringReturningMethods?.Select(m => m.Name).Distinct();

También puede compilar la consulta completa con la sintaxis de consulta LINQ y, después, mostrar todos los nombres de método en la consola:

foreach (string name in (from method in stringTypeSymbol?
                         .GetMembers().OfType<IMethodSymbol>()
                         where SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol) &&
                         method.DeclaredAccessibility == Accessibility.Public
                         select method.Name).Distinct())
{
    Console.WriteLine(name);
}

Compile y ejecute el programa. Debería aparecer la siguiente salida:

Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .

Ha usado la API semántica para buscar y mostrar información sobre los símbolos que forman parte de este programa.