Partage via


Commencez avec l’analyse sémantique

Ce didacticiel part du principe que vous connaissez l’API Syntax. L’article de prise en main de l’analyse de la syntaxe fournit une introduction suffisante.

Dans ce tutoriel, vous allez explorer les API Symbol et Binding. Ces API fournissent des informations sur la signification sémantique d’un programme. Ils vous permettent de poser et de répondre aux questions sur les types représentés par n’importe quel symbole dans votre programme.

Vous devez installer le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET :

Instructions d’installation - Visual Studio Installer

Il existe deux façons de trouver le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET dans Visual Studio Installer :

Installer à l’aide de l’affichage Visual Studio Installer - Charges de travail

Le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET n’est pas automatiquement sélectionné dans le cadre de la charge de travail de développement d’extension Visual Studio. Vous devez le sélectionner en tant que composant facultatif.

  1. Exécuter Visual Studio Installer
  2. Sélectionnez Modifier
  3. Choisissez la charge de travail Développement d’extensions Visual Studio.
  4. Ouvrez le nœud de développement d’extension Visual Studio dans l’arborescence récapitulative.
  5. Vérifiez que la case pour le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET est cochée.
  6. Sélectionnez Modifier.

Si vous le souhaitez, vous souhaiterez également que l’éditeur DGML affiche des graphiques dans le visualiseur :

  1. Ouvrez le nœud Composants individuels dans l’arborescence récapitulative.
  2. Cochez la case pour l’éditeur DGML

Installer à l’aide de l’onglet Visual Studio Installer - Composants individuels

  1. Exécuter Visual Studio Installer
  2. Sélectionnez Modifier
  3. Sélectionnez l’onglet Composants individuels
  4. Cochez la case pour le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET. Vous le trouverez en haut sous la section Compilateurs, outils de génération et runtimes .
  5. Sélectionnez Modifier.

Si vous le souhaitez, vous souhaiterez également que l’éditeur DGML affiche des graphiques dans le visualiseur :

  1. Cochez la case pour l’éditeur DGML. Vous le trouverez sous la section Outils de code .

Présentation des compilations et des symboles

Au fur et à mesure que vous travaillez davantage avec le Kit de développement logiciel (SDK) du compilateur .NET, vous connaissez les distinctions entre l’API syntaxique et l’API sémantique. L’API Syntax vous permet d’examiner la structure d’un programme. Toutefois, vous souhaitez souvent obtenir des informations plus riches sur la sémantique ou la signification d’un programme. Bien qu’un fichier de code ou un extrait de code Visual Basic ou C# puisse être analysé de manière syntactique en isolation, il n’est pas significatif de poser des questions telles que « quel est le type de cette variable » dans un vide. La signification d’un nom de type peut dépendre des références d’assembly, des importations d’espaces de noms ou d’autres fichiers de code. Ces questions sont répondues à l’aide de l’API sémantique, en particulier la Microsoft.CodeAnalysis.Compilation classe.

Une instance de Compilation est comparable à un seul projet tel qu’il est vu par le compilateur et représente tout ce qui est nécessaire pour compiler un programme Visual Basic ou C#. La compilation inclut l’ensemble de fichiers sources à compiler, les références d’assembly et les options du compilateur. Vous pouvez raisonner sur la signification du code à l’aide de toutes les autres informations de ce contexte. A Compilation vous permet de rechercher des symboles : entités telles que des types, des espaces de noms, des membres et des variables auxquelles les noms et d’autres expressions font référence. Le processus d’association de noms et d’expressions avec Symboles est appelé Liaison.

Comme Microsoft.CodeAnalysis.SyntaxTree, Compilation est une classe abstraite avec des dérivés spécifiques au langage. Lors de la création d’une instance de compilation, vous devez appeler une méthode de fabrique sur la Microsoft.CodeAnalysis.CSharp.CSharpCompilation classe (ou Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Requête de symboles

Dans ce tutoriel, vous examinez à nouveau le programme « Hello World ». Cette fois, vous interrogez les symboles dans le programme pour comprendre les types de ces symboles. Vous interrogez les types dans un espace de noms et apprenez à rechercher les méthodes disponibles sur un type.

Vous pouvez voir le code terminé pour cet exemple dans notre dépôt GitHub.

Note

Les types d’arborescence de syntaxe utilisent l’héritage pour décrire les différents éléments de syntaxe valides à différents emplacements du programme. L’utilisation de ces API signifie souvent la conversion de propriétés ou de membres de collection vers des types dérivés spécifiques. Dans les exemples suivants, l’affectation et les casts sont des instructions distinctes, à l’aide de variables typées explicitement. Vous pouvez lire le code pour afficher les types de retour de l’API et le type d’exécution des objets retournés. Dans la pratique, il est plus courant d’utiliser des variables implicitement typées et de s’appuyer sur les noms d’API pour décrire le type d’objets examinés.

Créez un projet C# Outil d'analyse de code autonome :

  • Dans Visual Studio, choisissez Fichier>nouveau>projet pour afficher la boîte de dialogue Nouveau projet.
  • Sous Visual C#>Extensibilité, choisissez Stand-Alone Outil d’analyse du code.
  • Nommez votre projet « SemanticQuickStart », puis cliquez sur OK.

Vous allez analyser le programme « Hello World ! » de base présenté précédemment. Ajoutez le texte du programme Hello World en tant que constante dans votre Program classe :

        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!"");
        }
    }
}";

Ensuite, ajoutez le code suivant pour générer l’arborescence de syntaxe du texte du code dans la programText constante. Ajoutez la ligne suivante à votre Main méthode :

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Ensuite, générez un élément CSharpCompilation à partir de l’arborescence que vous avez déjà créée. L'exemple « Hello World » s'appuie sur les types String et Console. Vous devez référencer l’assembly qui déclare ces deux types dans votre compilation. Ajoutez la ligne suivante à votre Main méthode pour créer une compilation de votre arborescence de syntaxe, y compris la référence à l’assembly approprié :

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

La CSharpCompilation.AddReferences méthode ajoute des références à la compilation. La MetadataReference.CreateFromFile méthode charge un assembly en tant que référence.

Interrogation du modèle sémantique

Une fois que vous avez un Compilation, vous pouvez lui demander un SemanticModel pour n’importe quel SyntaxTree contenu dans ce Compilation. Vous pouvez considérer le modèle sémantique comme la source de toutes les informations que vous obtiendriez normalement à partir d’IntelliSense. Un SemanticModel peut répondre à des questions telles que « Quels noms sont dans la portée à cet emplacement ? », « Quels membres sont accessibles à partir de cette méthode ? », « Quelles variables sont utilisées dans ce bloc de texte ? » et « À quoi fait référence ce nom/expression ? » Ajoutez cette instruction pour créer le modèle sémantique :

SemanticModel model = compilation.GetSemanticModel(tree);

Liaison d’un nom

Le Compilation crée le SemanticModel à partir du SyntaxTree. Après avoir créé le modèle, vous pouvez l’interroger pour rechercher la première using directive et récupérer les informations de symbole pour l’espace System de noms. Ajoutez ces deux lignes à votre Main méthode pour créer le modèle sémantique et récupérer le symbole de la première using directive :

// 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);

Le code précédent montre comment lier le nom dans la première directive using pour récupérer un Microsoft.CodeAnalysis.SymbolInfo spécifiquement pour l'espace de noms System. Le code précédent illustre également que vous utilisez le modèle de syntaxe pour rechercher la structure du code ; vous utilisez le modèle sémantique pour comprendre sa signification. Le modèle de syntaxe recherche la chaîne System dans la using directive. Le modèle sémantique contient toutes les informations sur les types définis dans l’espace System de noms.

À partir de l’objet SymbolInfo, vous pouvez obtenir le Microsoft.CodeAnalysis.ISymbol à l'aide de la propriété SymbolInfo.Symbol. Cette propriété retourne le symbole auquel cette expression fait référence. Pour les expressions qui ne font référence à rien (comme les littéraux numériques), cette propriété est null. Quand la SymbolInfo.Symbol valeur n’est pas null, le ISymbol.Kind symbole indique le type du symbole. Dans cet exemple, la ISymbol.Kind propriété est un SymbolKind.Namespace. Ajoutez le code suivant à votre méthode Main. Il récupère le symbole de l'espace de noms System, puis affiche tous les espaces de noms enfants déclarés dans l'espace de noms System :

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

Exécutez le programme et vous devez voir la sortie suivante :

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 . . .

Note

La sortie n’inclut pas chaque espace de noms qui est un sous-espace de noms de l’espace de noms System. Il affiche chaque espace de noms présent dans cette compilation, qui ne fait référence qu’à l’assembly où System.String est déclaré. Les espaces de noms déclarés dans d'autres assemblies ne sont pas reconnus par cette compilation.

Liaison d’une expression

Le code précédent montre comment rechercher un symbole en liant à un nom. Il existe d’autres expressions dans un programme C# qui peuvent être liées qui ne sont pas des noms. Pour illustrer cette fonctionnalité, accédons à la liaison à un littéral de chaîne simple.

Le programme « Hello World » contient une Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxchaîne « Hello, World ! » affichée dans la console.

Vous trouvez la chaîne « Hello, World ! » en localisant l'expression littérale de chaîne unique dans le programme. Ensuite, une fois que vous avez localisé le nœud de syntaxe, obtenez les informations de type pour ce nœud à partir du modèle sémantique. Ajoutez le code suivant à votre Main méthode :

// 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);

Le struct Microsoft.CodeAnalysis.TypeInfo inclut une propriété qui permet TypeInfo.Type d’accéder aux informations sémantiques sur le type du littéral. Dans cet exemple, il s’agit du string type. Ajoutez une déclaration qui affecte cette propriété à une variable locale :

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Pour terminer ce tutoriel, créons une requête LINQ qui crée une séquence de toutes les méthodes publiques déclarées sur le string type qui retourne un string. Cette requête est complexe. Nous allons donc la générer ligne par ligne, puis la reconstruire en tant que requête unique. La source de cette requête est la séquence de tous les membres déclarés sur le string type :

var allMembers = stringTypeSymbol?.GetMembers();

Cette séquence source contient tous les membres, y compris les propriétés et les champs, afin de la filtrer en utilisant la méthode ImmutableArray<T>.OfType pour trouver les éléments qui sont des objets Microsoft.CodeAnalysis.IMethodSymbol :

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

Ensuite, ajoutez un autre filtre pour renvoyer uniquement les méthodes publiques et renvoyer un string:

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

Sélectionnez uniquement la propriété "name" et uniquement les noms distincts en éliminant les surcharges :

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

Vous pouvez également générer la requête complète à l’aide de la syntaxe de requête LINQ, puis afficher tous les noms de méthode dans la console :

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);
}

Générez et exécutez le programme. La sortie suivante doit s’afficher :

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 . . .

Vous avez utilisé l’API sémantique pour rechercher et afficher des informations sur les symboles qui font partie de ce programme.