Bien démarrer avec l’analyse sémantique
Ce tutoriel suppose que vous êtes familiarisé avec l’API Syntaxe. L’article Bien démarrer avec l’analyse syntaxique fournit une introduction suffisante.
Dans ce tutoriel, vous découvrez les API Symbole et Liaison. Ces API fournissent des informations sur la signification sémantique d’un programme. Elles vous permettent de poser et de répondre à des questions sur les types représentés par un symbole dans votre programme.
Vous devez installer le SDK .NET Compiler Platform :
Instructions d’installation - Visual Studio Installer
Vous pouvez rechercher SDK .NET Compiler Platform dans Visual Studio Installer de deux façons :
Vue Installation avec Visual Studio Installer - Charges de travail
SDK .NET Compiler Platform n’est pas sélectionné automatiquement dans le cadre de la charge de travail Développement d’extensions Visual Studio. Vous devez le sélectionner comme composant facultatif.
- Exécutez Visual Studio Installer.
- Sélectionnez Modifier
- Choisissez la charge de travail Développement d’extensions Visual Studio.
- Ouvrez le nœud Développement d’extensions Visual Studio dans l’arborescence résumée.
- Cochez la case pour SDK .NET Compiler Platform. Il se trouve en dernier sous les composants facultatifs.
Si vous le souhaitez, vous pouvez ajouter l’éditeur DGML pour afficher des graphes dans le visualiseur :
- Ouvrez le nœud Composants individuels dans l’arborescence résumée.
- Cochez la case pour Éditeur DGML.
Onglet Installation avec Visual Studio Installer - Composants individuels
- Exécutez Visual Studio Installer.
- Sélectionnez Modifier
- Sélectionnez l’onglet Composants individuels.
- Cochez la case pour SDK .NET Compiler Platform. Il se trouve en haut de la liste, sous la section Compilateurs, outils de génération et runtimes.
Si vous le souhaitez, vous pouvez ajouter l’éditeur DGML pour afficher des graphes dans le visualiseur :
- Cochez la case pour Éditeur DGML. Il se trouve sous la section Outils de code.
Présentation des compilations et des symboles
Au fil de votre utilisation du SDK du compilateur .NET, vous allez mieux comprendre les différences entre l’API Syntaxe et l’API Sémantique. L’API Syntaxe vous permet d’examiner la structure d’un programme. Cependant, des informations plus détaillées sur la sémantique ou la signification d’un programme sont souvent nécessaires. Si la syntaxe d’un fichier ou d’un extrait de code autonome Visual Basic ou C# peut être analysé de façon isolée, cela ne veut pas dire grand-chose de poser des questions comme « quel est le type de cette variable ?» dans le vide. La signification d’un nom de type peut dépendre de références d’assembly, d’importations d’espaces de noms ou d’autres fichiers de code. Ces questions sont traitées avec l’API Sémantique, plus précisément avec la classe Microsoft.CodeAnalysis.Compilation.
Une instance de Compilation est analogue à un 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 comprend l’ensemble des fichiers sources à compiler, les références d’assembly et les options du compilateur. Vous pouvez analyser la signification du code en utilisant toutes les autres informations de ce contexte. Une Compilation vous permet de rechercher des symboles, qui sont des entités comme des types, des espaces de noms, des membres et des variables, qui sont référencées par des noms et d’autres expressions. Le processus consistant à associer des noms et des expressions à des symboles est appelé la liaison.
Comme Microsoft.CodeAnalysis.SyntaxTree, Compilation est une classe abstraite avec des dérivés spécifiques à un langage. Quand vous créez une instance de compilation, vous devez appeler une méthode de fabrique sur la classe Microsoft.CodeAnalysis.CSharp.CSharpCompilation (ou Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).
Interrogation des symboles
Dans ce tutoriel, vous examinez à nouveau le programme « Hello World ». Cette fois-ci, vous interrogez les symboles du programme pour comprendre quels types ces symboles représentent. Vous interrogez les types dans un espace de noms et vous découvrez comment trouver les méthodes disponibles sur un type.
Vous pouvez trouver le code complet de cet exemple dans notre dépôt GitHub.
Notes
Les types de l’arborescence de syntaxe utilisent l’héritage pour décrire les différents éléments de syntaxe qui sont valides à différents emplacements du programme. Utiliser ces API signifie souvent effectuer un cast des propriétés ou des membres de collection vers des types dérivés spécifiques. Dans les exemples suivants, l’affectation et les casts sont des instructions distinctes, qui utilisent des variables typées explicitement. Vous pouvez lire le code pour voir les types de retour de l’API et le type à l’exécution des objets retournés. Dans la pratique, il est plus courant d’utiliser des variables typées implicitement et de se baser sur des noms d’API pour décrire le type des objets examinés.
Créez un projet C# Outil d’analyse du code autonome :
- Dans Visual Studio, choisissez Fichier>Nouveau>Projet pour afficher la boîte de dialogue Nouveau projet.
- Sous Visual C#>Extensibilité, choisissez Outil d’analyse du code autonome.
- Nommez votre projet « SemanticQuickStart » et cliquez sur OK.
Vous allez analyser le programme « Hello World ! » de base indiqué plus haut.
Ajoutez le texte pour le programme Hello World en tant que constante dans votre classe 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!"");
}
}
}";
Ensuite, ajoutez le code suivant pour générer l’arborescence de syntaxe pour le texte du code dans la constante programText
. Ajoutez la ligne suivante à votre méthode Main
:
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Ensuite, générez une CSharpCompilation à partir de l’arborescence que vous avez déjà créée. L’exemple « Hello World » est basé 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 méthode Main
pour créer une compilation de votre arborescence de syntaxe, en incluant la référence à l’assembly approprié :
var compilation = CSharpCompilation.Create("HelloWorld")
.AddReferences(MetadataReference.CreateFromFile(
typeof(string).Assembly.Location))
.AddSyntaxTrees(tree);
La méthode CSharpCompilation.AddReferences ajoute des références à la compilation. La méthode MetadataReference.CreateFromFile charge un assembly en tant que référence.
Interrogation du modèle sémantique
Une fois que vous avez une Compilation, vous pouvez la demander pour un SemanticModel pour n’importe quelle SyntaxTree contenue dans Compilation. Vous pouvez considérer le modèle sémantique comme étant la source de toutes les informations que vous obtiendriez normalement d’IntelliSense. Un SemanticModel peut répondre à des questions comme « Quels noms se trouvent dans l’étendue à cet emplacement ? », « Quels membres sont accessibles à partir de cette méthode ? », « Quelles variables sont utilisées dans ce bloc de texte ? » et « À quoi ce nom ou cette expression font-ils référence ? » Ajoutez cette déclaration pour créer un modèle sémantique :
SemanticModel model = compilation.GetSemanticModel(tree);
Liaison d’un nom
Le Compilation crée SemanticModel à partir de SyntaxTree. Après avoir créé le modèle, vous pouvez l’interroger pour rechercher la première directive using
et récupérer les informations des symboles pour l’espace de noms System
. Ajoutez ces deux lignes à votre méthode Main
pour créer le modèle sémantique et pour récupérer le symbole pour la première instruction 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);
Le code précédent montre comment lier le nom dans la première directive using
pour récupérer un Microsoft.CodeAnalysis.SymbolInfo pour l’espace de noms System
. Le code précédent montre aussi que vous utilisez le modèle de syntaxe pour trouver 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 l’instruction using. Le modèle sémantique a toutes les informations sur les types définis dans l’espace de noms System
.
À partir de l’objet SymbolInfo, vous pouvez obtenir Microsoft.CodeAnalysis.ISymbol en utilisant 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 SymbolInfo.Symbol n’est pas null, ISymbol.Kind indique le type du symbole. Dans cet exemple, la propriété ISymbol.Kind est un SymbolKind.Namespace. Ajoutez le code suivant à votre méthode Main
. Il récupère le symbole pour 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 ; vous devez normalement 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 . . .
Notes
La sortie n’inclut pas chaque espace de noms qui est un espace de noms enfant de l’espace de noms System
. Elle affiche chaque espace de noms qui est présent dans cette compilation, qui référence seulement l’assembly où System.String
est déclaré. Les espaces de noms déclarés dans d’autres assemblys ne sont pas connus de cette compilation.
Liaison d’une expression
Le code précédent montre comment trouver un symbole en le liant à un nom. D’autres expressions existent dans un programme C# qui peuvent être liées, mais qui ne sont pas des noms. Pour montrer cette possibilité, accédons à la liaison vers un littéral de chaîne simple.
Le programme « Hello World » contient un Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax, la chaîne « Hello, World! » affichée dans la console.
Vous trouvez la chaîne « Hello World » en recherchant le seul littéral de chaîne dans le programme. Ensuite, une fois que vous avez localisé le nœud de syntaxe, vous obtenez les informations de type pour ce nœud auprès du modèle sémantique. Ajoutez le code suivant à votre méthode 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 struct Microsoft.CodeAnalysis.TypeInfo inclut une propriété TypeInfo.Type qui permet d’accéder aux informations sémantiques sur le type du littéral. Dans cet exemple, il s’agit du type string
. Ajoutez une déclaration qui affecte cette propriété à une variable locale :
var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;
Pour terminer ce tutoriel, construisons une requête LINQ qui crée une séquence de toutes les méthodes publiques déclarées sur le type string
qui retournent un type string
. Cette requête est complexe : nous allons donc la construire ligne par ligne, puis la reconstruire pour former une seule requête. La source de cette requête est la séquence de tous les membres déclarés sur le type string
:
var allMembers = stringTypeSymbol?.GetMembers();
Cette séquence source contient tous les membres, notamment les propriétés et les champs ; filtrez-la avec 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 retourner seulement les méthodes qui sont publiques et qui retournent un type string
:
var publicStringReturningMethods = methods?
.Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType, stringTypeSymbol) &&
m.DeclaredAccessibility == Accessibility.Public);
Sélectionnez seulement la propriété name et seulement les noms distincts en supprimant les surcharges :
var distinctMethods = publicStringReturningMethods?.Select(m => m.Name).Distinct();
Vous pouvez aussi créer toute la requête avec 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. Vous devez normalement voir la sortie suivante :
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.