Partage via


Analyseurs Roslyn et bibliothèque sensible au code pour les ImmutableArrays

Le .NET Compiler Platform (« Roslyn ») vous aide à créer des bibliothèques prenant en charge le code. Une bibliothèque prenant en charge le code fournit des fonctionnalités que vous pouvez utiliser et outils (analyseurs Roslyn) pour vous aider à utiliser la bibliothèque de la meilleure façon ou pour éviter les erreurs. Cette rubrique vous montre comment créer un analyseur Roslyn réel pour détecter les erreurs courantes lors de l’utilisation du package NuGet System.Collections.Immutable. L’exemple montre également comment fournir un correctif de code pour un problème de code trouvé par l’analyseur. Les utilisateurs voient les correctifs de code dans l’interface utilisateur de l’ampoule Visual Studio et peuvent appliquer automatiquement un correctif pour le code.

Démarrer

Vous avez besoin des éléments suivants pour générer cet exemple :

  • Visual Studio 2015 (pas une édition Express) ou une version ultérieure. Vous pouvez utiliser la version gratuite de Visual Studio Community
  • Visual Studio SDK. Vous pouvez également, lors de l’installation de Visual Studio, vérifier Outils d’extensibilité Visual Studio sous Common Tools pour installer le Kit de développement logiciel (SDK) en même temps. Si vous avez déjà installé Visual Studio, vous pouvez également installer ce Kit de développement logiciel (SDK) en accédant au menu principal Fichier>Nouveau>Project, en choisissant C# dans le volet de navigation gauche, puis en choisissant extensibilité. Lorsque vous choisissez le modèle de projet chemin de navigation «Install the Visual Studio Extensibility Tools», il vous invite à télécharger et à installer le Kit de développement logiciel (SDK).
  • .NET Compiler Platform (« Roslyn ») SDK. Vous pouvez également installer ce Kit de développement logiciel (SDK) en accédant au menu principal Fichier>Nouveau>Project, en choisissant C# dans le volet de navigation gauche, puis en choisissant extensibilité. Lorsque vous choisissez le modèle de projet «Télécharger le Kit de développement logiciel (SDK) .NET Compiler Platform» de type 'breadcrumb', il vous invite à télécharger et installer le SDK. Ce KIT SDK inclut le visualiseur de syntaxe Roslyn . Cet outil utile vous aide à déterminer les types de modèles de code que vous devez rechercher dans votre analyseur. L’infrastructure de l’analyseur appelle votre code pour des types de modèles de code spécifiques, de sorte que votre code s’exécute uniquement si nécessaire et ne peut se concentrer que sur l’analyse du code approprié.

Quel est le problème?

Supposons que vous fournissiez une bibliothèque avec la prise en charge d’ImmutableArray (par exemple, System.Collections.Immutable.ImmutableArray<T>). Les développeurs C# ont beaucoup d’expérience avec les tableaux .NET. Toutefois, en raison de la nature des techniques ImmutableArrays et d’optimisation utilisées dans l’implémentation, les intuitions des développeurs C# entraînent l’écriture de code rompu par les utilisateurs de votre bibliothèque, comme expliqué ci-dessous. En outre, les utilisateurs ne voient pas leurs erreurs avant l'exécution, ce qui n'est pas l'expérience de qualité à laquelle ils sont habitués avec Visual Studio et .NET.

Les utilisateurs sont familiarisés avec l’écriture de code comme suit :

var a1 = new int[0];
Console.WriteLine("a1.Length = {0}", a1.Length);
var a2 = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine("a2.Length = {0}", a2.Length);

La création de tableaux vides pour remplir les lignes de code suivantes et l’utilisation de la syntaxe d’initialiseur de collection sont familières aux développeurs C#. Toutefois, l’écriture du même code pour un ImmutableArray se bloque au moment de l’exécution :

var b1 = new ImmutableArray<int>();
Console.WriteLine("b1.Length = {0}", b1.Length);
var b2 = new ImmutableArray<int> { 1, 2, 3, 4, 5 };
Console.WriteLine("b2.Length = {0}", b2.Length);

La première erreur est due à l'implémentation de ImmutableArray en utilisant une structure pour envelopper le stockage de données sous-jacent. Les structs doivent avoir des constructeurs sans paramètres afin que les expressions default(T) puissent renvoyer des structs avec tous les membres à zéro ou null. Lorsque le code accède à b1.Length, il existe une erreur de déréférencement null au moment de l’exécution, car il n’existe aucun tableau de stockage sous-jacent dans le struct ImmutableArray. La bonne façon de créer un ImmutableArray vide est ImmutableArray<int>.Empty.

L’erreur avec les initialiseurs de collection se produit, car la méthode ImmutableArray.Add retourne de nouvelles instances chaque fois que vous l’appelez. Étant donné que ImmutableArrays ne change jamais, lorsque vous ajoutez un nouvel élément, vous récupérez un nouvel objet ImmutableArray (qui peut partager le stockage pour des raisons de performances avec un ImmutableArray existant précédemment). Étant donné que b2 pointe vers le premier ImmutableArray avant d’appeler Add() cinq fois, b2 est un ImmutableArray par défaut. L’appel de longueur sur celui-ci se bloque également avec une erreur de déréférencement null. La bonne façon d’initialiser un ImmutableArray sans appeler manuellement Add consiste à utiliser ImmutableArray.CreateRange(new int[] {1, 2, 3, 4, 5}).

Rechercher des types de nœuds de syntaxe pertinents pour déclencher votre analyseur

Pour commencer à générer l’analyseur, commencez par déterminer le type de SyntaxNode que vous devez rechercher. Lancez le visualiseur de syntaxe à partir du menu Afficher>Autres fenêtres>Roslyn Syntax Visualizer.

Placez le point d’insertion de l’éditeur sur la ligne qui déclare b1. Le Visualiseur de Syntaxe indique que vous vous trouvez dans un nœud LocalDeclarationStatement de l'arborescence de syntaxe. Ce nœud a un VariableDeclaration, qui à son tour a un VariableDeclarator, qui à son tour a un EqualsValueClause, et enfin il y a un ObjectCreationExpression. Lorsque vous cliquez dans l’arborescence de nœuds du visualiseur de syntaxe, la syntaxe dans la fenêtre de l’éditeur est mise en surbrillance afin d'afficher le code représenté par ce nœud. Les noms des sous-types SyntaxNode correspondent aux noms utilisés dans la grammaire C#.

Créer le projet d’analyseur

Dans le menu principal, choisissez Fichier>Nouveau>Projet. Dans la boîte de dialogue Nouveau projet, sous projets C# dans la barre de navigation gauche, choisissez Extensibilité, puis, dans le volet droit, choisissez l’analyseur avec le modèle de projet Correctif de code. Entrez un nom et confirmez la boîte de dialogue.

Le modèle ouvre un fichier DiagnosticAnalyzer.cs. Choisissez cet onglet de mémoire tampon de l’éditeur. Ce fichier a une classe d’analyseur (formée à partir du nom que vous avez donné au projet) qui dérive de DiagnosticAnalyzer (type d’API Roslyn). Votre nouvelle classe a une DiagnosticAnalyzerAttribute déclarant que votre analyseur est pertinent pour le langage C# afin que le compilateur découvre et charge votre analyseur.

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ImmutableArrayAnalyzer : DiagnosticAnalyzer
{}

Vous pouvez implémenter un analyseur à l’aide de Visual Basic qui cible le code C#, et vice versa. Il est plus important dans DiagnosticAnalyzerAttribute de choisir si votre analyseur cible un langage ou les deux. Les analyseurs plus sophistiqués qui nécessitent une modélisation détaillée du langage ne peuvent cibler qu’une seule langue. Si votre analyseur, par exemple, ne vérifie que les noms de types ou les noms de membres publics, il peut être possible d'utiliser le modèle de langage commun offert par Roslyn pour Visual Basic et C#. Par exemple, FxCop avertit qu’une classe implémente ISerializable, mais que la classe n’a pas l’attribut SerializableAttribute, ce qui est indépendant du langage et fonctionne tant pour le code Visual Basic que pour le code C#.

Initialiser l’analyseur

Faites défiler vers le bas un peu dans la classe DiagnosticAnalyzer pour voir la méthode Initialize. Le compilateur appelle cette méthode lors de l’activation d’un analyseur. La méthode prend un objet AnalysisContext qui permet à votre analyseur d’obtenir des informations de contexte et d’inscrire des rappels pour les événements pour les types de code que vous souhaitez analyser.

public override void Initialize(AnalysisContext context) {}

Ouvrez une nouvelle ligne dans cette méthode et tapez « context. » pour voir une liste de complétion IntelliSense. Vous pouvez voir dans la liste de complétion qu'il y a de nombreuses méthodes Register... pour gérer différents types d'événements. Par exemple, la première (RegisterCodeBlockAction) rappelle votre code pour un bloc, qui est généralement du code entre accolades. L'enregistrement d'un bloc réintroduit également votre code pour l'initialiseur d'un champ, la valeur attribuée à un attribut, ou la valeur d'un paramètre optionnel.

Comme autre exemple, RegisterCompilationStartAction, renvoie à votre code au début d’une compilation, ce qui est utile lorsque vous devez collecter l’état sur de nombreux emplacements. Vous pouvez créer une structure de données, par exemple, pour collecter tous les symboles utilisés, et chaque fois que votre analyseur est rappelé pour une syntaxe ou un symbole, vous pouvez enregistrer des informations sur chaque emplacement de votre structure de données. Lorsque la compilation est terminée, vous pouvez analyser tous les emplacements que vous avez enregistrés, par exemple, pour signaler les symboles utilisés par le code dans chaque instruction using.

À l’aide du visualiseur de syntaxe , vous avez déterminé que vous souhaitez être appelé lorsque le compilateur traite un ObjectCreationExpression. Vous utilisez ce code pour configurer le rappel :

context.RegisterSyntaxNodeAction(c => AnalyzeObjectCreation(c),
                                 SyntaxKind.ObjectCreationExpression);

Vous vous inscrivez à un nœud de syntaxe et filtrez uniquement pour les nœuds de syntaxe de création d’objets. Par convention, les auteurs d’analyseurs utilisent une fonction lambda lors de l’enregistrement d’actions, ce qui permet de maintenir les analyseurs sans état. Vous pouvez utiliser la fonctionnalité Visual Studio Générer à partir de l’utilisation pour créer la méthode AnalyzeObjectCreation. Cela génère également le type de paramètre de contexte approprié.

Définir des propriétés pour les utilisateurs de votre analyseur

Afin que votre analyseur s’affiche correctement dans l’interface utilisateur de Visual Studio, recherchez et modifiez la ligne de code suivante pour identifier votre analyseur :

internal const string Category = "Naming";

Remplacez "Naming" par "API Guidance".

Ensuite, recherchez et ouvrez le fichier Resources.resx dans votre projet à l'aide de l'Explorateur de solutions . Vous pouvez placer une description pour votre analyseur, votre titre, etc. Vous pouvez modifier la valeur de tous ces éléments en "Don't use ImmutableArray<T> constructor" pour l’instant. Vous pouvez placer des arguments de mise en forme de chaîne dans votre chaîne ({0}, {1}, etc.) et ultérieurement lorsque vous appelez Diagnostic.Create(), vous pouvez fournir un tableau params d’arguments à passer.

Analyser une expression de création d’objet

La méthode AnalyzeObjectCreation prend un autre type de contexte fourni par l’infrastructure d’analyseur de code. Le AnalysisContext de la méthode Initialize vous permet d’inscrire des rappels d’action pour configurer votre analyseur. Le SyntaxNodeAnalysisContext, par exemple, a un CancellationToken que vous pouvez contourner. Si un utilisateur commence à taper dans l’éditeur, Roslyn annule l’exécution des analyseurs pour enregistrer le travail et améliorer les performances. Comme autre exemple, ce contexte a une propriété Node qui retourne le nœud de syntaxe de création d’objet.

Obtenez le nœud, que vous pouvez supposer est le type pour lequel vous avez filtré l’action de nœud de syntaxe :

var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

Lancez Visual Studio avec votre analyseur la première fois

Lancez Visual Studio en créant et en exécutant votre analyseur (appuyez sur F5). Étant donné que le projet de démarrage dans l’Explorateur de solutions est le projet VSIX, l’exécution de votre code génère votre code et un VSIX, puis lance Visual Studio avec ce VSIX installé. Lorsque vous lancez Visual Studio de cette façon, il démarre avec une ruche de Registre distincte afin que votre utilisation principale de Visual Studio ne soit pas affectée par vos instances de test lors de la génération d’analyseurs. La première fois que vous lancez cette méthode, Visual Studio effectue plusieurs initialisations similaires au moment où vous avez lancé Visual Studio après l’avoir installée.

Créez un projet de console, puis entrez le code de tableau dans la méthode Main de vos applications console :

var b1 = new ImmutableArray<int>();
Console.WriteLine("b1.Length = {0}", b1.Length);
var b2 = new ImmutableArray<int> { 1, 2, 3, 4, 5 };
Console.WriteLine("b2.Length = {0}", b2.Length);

Les lignes de code avec ImmutableArray présentent des lignes ondulées, car vous devez obtenir le package NuGet immuable et ajouter une instruction using à votre code. Appuyez sur le bouton de pointeur droit sur le nœud du projet dans l’Explorateur de solutions , puis choisissez Gérer les packages NuGet. Dans le gestionnaire NuGet, tapez « Immuable » dans la zone de recherche, puis choisissez l’élément System.Collections.Immutable (ne choisissez pas Microsoft.Bcl.Immutable) dans le volet gauche, puis appuyez sur le bouton Installer dans le volet droit. L’installation du package ajoute une référence à vos références de projet.

Vous voyez toujours des lignes ondulées rouges sous ImmutableArray, et devez donc placer le point d’insertion dans cet identificateur et appuyer sur Ctrl+. (point) pour afficher le menu correctif suggéré et choisir d’ajouter l’instruction using appropriée.

Enregistrer tout et fermer la deuxième instance de Visual Studio pour le moment afin de vous mettre dans un état propre pour continuer.

Terminer l'analyseur en utilisant Modifier et Continuer

Dans la première instance de Visual Studio, définissez un point d’arrêt au début de votre méthode de AnalyzeObjectCreation en appuyant sur F9 avec le curseur sur la première ligne.

Lancez à nouveau votre analyseur avec F5 et, dans la deuxième instance de Visual Studio, rouvrez votre application console que vous avez créée la dernière fois.

Vous revenez à la première instance de Visual Studio au point d'arrêt, car le compilateur Roslyn a détecté une expression de création d'objet et a appelé votre analyseur.

Obtenez le nœud de création d’objet. Effectuez un pas à pas sur la ligne qui définit la variable objectCreation en appuyant sur F10, puis, dans la fenêtre Exécution, évaluez l’expression "objectCreation.ToString()". Vous voyez que le nœud de syntaxe vers lequel la variable pointe est le code "new ImmutableArray<int>()", juste ce que vous recherchez.

Obtenir un objet de type ImmutableArray<T>. Vous devez vérifier si le type créé est ImmutableArray. Tout d’abord, vous obtenez l’objet qui représente ce type. Vous vérifiez les types à l’aide du modèle sémantique pour vous assurer que vous disposez exactement du type approprié et que vous ne comparez pas la chaîne de ToString(). Entrez la ligne de code suivante à la fin de la fonction :

var immutableArrayOfTType =
    context.SemanticModel
           .Compilation
           .GetTypeByMetadataName("System.Collections.Immutable.ImmutableArray`1");

Vous désignez des types génériques dans les métadonnées avec des accents graves (`) et le nombre de paramètres génériques. C’est pourquoi vous ne voyez pas "... ImmutableArray<T>» dans le nom des métadonnées.

Le modèle sémantique comporte de nombreuses choses utiles qui vous permettent de poser des questions sur les symboles, le flux de données, la durée de vie des variables, etc. Roslyn sépare les nœuds de syntaxe du modèle sémantique pour différentes raisons d’ingénierie (performances, modélisation de code erroné, etc.). Vous souhaitez que le modèle de compilation recherche des informations contenues dans les références pour une comparaison précise.

Vous pouvez faire glisser le pointeur d’exécution jaune sur le côté gauche de la fenêtre de l’éditeur. Faites-le glisser jusqu’à la ligne qui définit la variable objectCreation et effectuez un pas à pas sur votre nouvelle ligne de code à l’aide de F10. Si vous pointez le pointeur de la souris sur la variable immutableArrayOfType, vous voyez que nous avons trouvé le type exact dans le modèle sémantique.

Obtenez le type de l’expression de création d’objet. « Type » est utilisé de plusieurs façons dans cet article, mais cela signifie que si vous avez une expression « new Foo », vous devez obtenir un modèle de Foo. Vous devez obtenir le type de l’expression de création d’objet pour voir s’il s’agit du type ImmutableArray<T>. Utilisez à nouveau le modèle sémantique pour obtenir des informations de symbole pour le symbole de type (ImmutableArray) dans l’expression de création d’objet. Entrez la ligne de code suivante à la fin de la fonction :

var symbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation.Type).Symbol as INamedTypeSymbol;

Étant donné que votre analyseur doit gérer du code incomplet ou incorrect dans les tampons d'édition (par exemple, une instruction using est manquante), vous devez vérifier si symbolInfo est null. Vous devez obtenir un type nommé (INamedTypeSymbol) à partir de l’objet d’informations de symboles pour terminer l’analyse.

Comparez les types. Étant donné qu’il existe un type générique ouvert de T que nous recherchons, et que le type dans le code est un type générique concret, vous interrogez les informations de symbole pour ce que le type est construit à partir (un type générique ouvert) et comparez ce résultat à immutableArrayOfTType. Entrez les éléments suivants à la fin de la méthode :

if (symbolInfo != null &&
    symbolInfo.ConstructedFrom.Equals(immutableArrayOfTType))
{}

Signalez le diagnostic. Rapporter le diagnostic est assez facile. Vous utilisez la règle créée pour vous dans le modèle de projet, qui est défini avant la méthode Initialize. Étant donné que cette situation dans le code est une erreur, vous pouvez modifier la ligne qui initialise la Règle pour remplacer DiagnosticSeverity.Warning (zigzag vert) par DiagnosticSeverity.Error (zigzag rouge). Le reste de la règle s'initialise à partir des ressources que vous avez modifiées au début du guide. Vous devez également signaler l’emplacement du soulignement ondulé, qui est l’emplacement de la spécification du type de l’expression de création de l’objet. Entrez ce code dans le bloc if :

context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Type.GetLocation()));

Votre fonction doit ressembler à ceci (peut-être mis en forme différemment) :

private void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context)
{
    var objectCreation = (ObjectCreationExpressionSyntax)context.Node;
    var immutableArrayOfTType =
        context.SemanticModel
               .Compilation
               .GetTypeByMetadataName(
                   "System.Collections.Immutable.ImmutableArray`1");
    var symbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation.Type).Symbol as
        INamedTypeSymbol;
    if (symbolInfo != null &&
        symbolInfo.ConstructedFrom.Equals(immutableArrayOfTType))
    {
        context.ReportDiagnostic(
            Diagnostic.Create(Rule, objectCreation.Type.GetLocation()));
    }
}

Supprimez le point d’arrêt pour que vous puissiez voir votre analyseur fonctionner (et arrêter de revenir à la première instance de Visual Studio). Faites glisser le pointeur d’exécution au début de votre méthode, puis appuyez sur F5 pour continuer l’exécution. Lorsque vous revenez à la deuxième instance de Visual Studio, le compilateur commence à examiner à nouveau le code, et il appellera votre analyseur. Vous pouvez voir un soulignement ondulé sous ImmutableType<int>.

Ajout d’un « correctif de code » pour le problème de code

Avant de commencer, fermez la deuxième instance de Visual Studio et arrêtez le débogage dans la première instance de Visual Studio (où vous développez l’analyseur).

Ajoutez une nouvelle classe. Utilisez le menu contextuel (bouton pointeur droit) sur le nœud de votre projet dans l’Explorateur de solutions et choisissez d’ajouter un nouvel élément. Ajoutez une classe appelée BuildCodeFixProvider. Cette classe doit dériver de CodeFixProvider, et vous devez utiliser Ctrl+. (point) pour appeler le correctif de code qui ajoute l’instruction using correcte. Cette classe doit également être annotée avec ExportCodeFixProvider attribut, et vous devez ajouter une instruction using pour résoudre l’énumération LanguageNames. Vous devez disposer d’un fichier de classe avec le code suivant :

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;

namespace ImmutableArrayAnalyzer
{
    [ExportCodeFixProvider(LanguageNames.CSharp)]
    class BuildCodeFixProvider : CodeFixProvider
    {}

Remplacer par des stubs les membres dérivés. À présent, placez le point d’insertion de l’éditeur dans l’identificateur CodeFixProvider et appuyez sur Ctrl+. (point) pour remplacer par des stubs l’implémentation de cette classe de base abstraite. Cela génère pour vous une propriété et une méthode.

Implémentez la propriété. Complétez le corps get de la propriété FixableDiagnosticIds avec le code suivant :

return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);

Roslyn rassemble les diagnostics et les correctifs en associant ces identificateurs, qui sont simplement des chaînes. Le modèle de projet a généré un ID de diagnostic pour vous et vous êtes libre de le modifier. Le code de la propriété retourne simplement l’ID de la classe d’analyseur.

La méthode RegisterCodeFixAsync prend un contexte. Le contexte est important, car un correctif de code peut s’appliquer à plusieurs diagnostics, ou il peut y avoir plusieurs problèmes sur une ligne de code. Si vous tapez « context » dans le corps de la méthode, la liste de saisie semi-automatique IntelliSense vous propose certains membres utiles. Il existe un membre CancellationToken que vous pouvez vérifier pour voir si quelque chose souhaite annuler le correctif. Il existe un membre Document qui comprend de nombreux membres utiles et vous permet d’accéder aux objets du modèle de projet et de solution. Il y a un membre Span qui représente le début et la fin de l'emplacement de code spécifié lorsque vous avez signalé le diagnostic.

Faites en sorte que la méthode soit asynchrone. La première chose à faire est de corriger la déclaration de méthode générée pour qu’elle soit une méthode async. Le correctif de code pour remplacer par des stubs l'implémentation d'une classe abstraite n'inclut pas le mot-clé async, même si la méthode renvoie une Task.

Obtenez la racine de l’arborescence de syntaxe. Pour modifier le code, vous devez produire une nouvelle arborescence de syntaxe avec les modifications apportées à votre correctif de code. Vous avez besoin du Document du contexte pour appeler GetSyntaxRootAsync. Il s’agit d’une méthode asynchrone, car il existe un travail inconnu pour obtenir l’arborescence de syntaxe, y compris l’obtention du fichier à partir du disque, l’analyse et la création du modèle de code Roslyn pour celui-ci. L’interface utilisateur de Visual Studio doit être réactive pendant cette période, ce qui permet d’utiliser async. Remplacez la ligne de code dans la méthode par les éléments suivants :

var root = await context.Document
                        .GetSyntaxRootAsync(context.CancellationToken);

Recherchez le nœud avec le problème. Vous repassez l’étendue du contexte, mais le nœud que vous trouvez n’est peut-être pas le code que vous devez modifier. Le diagnostic signalé ne fournissait que l'étendue de l'identificateur de type (où se trouvait le soulignement ondulé), mais vous devez remplacer l'expression de création d'objet dans son intégralité, y compris le new mot-clé au début et les parenthèses à la fin. Ajoutez le code suivant à votre méthode (et utilisez Ctrl+. pour ajouter une instruction using pour ObjectCreationExpressionSyntax) :

var objectCreation = root.FindNode(context.Span)
                         .FirstAncestorOrSelf<ObjectCreationExpressionSyntax>();

Inscrivez votre correctif de code pour l’interface utilisateur de l’ampoule. Lorsque vous inscrivez votre correctif de code, Roslyn se connecte automatiquement à l’interface utilisateur de l’ampoule Visual Studio. Les utilisateurs finaux verront qu’ils peuvent utiliser Ctrl+. (point) lorsque votre analyseur souligne de manière ondulée une utilisation incorrecte du constructeur ImmutableArray<T>. Étant donné que votre fournisseur de correctif de code s’exécute uniquement en cas de problème, vous pouvez supposer que vous avez l’expression de création d’objet que vous recherchiez. À partir du paramètre de contexte, vous pouvez inscrire le nouveau correctif de code en ajoutant le code suivant à la fin de RegisterCodeFixAsync méthode :

context.RegisterCodeFix(
            CodeAction.Create("Use ImmutableArray<T>.Empty",
                              c => ChangeToImmutableArrayEmpty(objectCreation,
                                                               context.Document,
                                                               c)),
            context.Diagnostics[0]);

Vous devez placer le point d’insertion de l’éditeur dans l’identificateur (CodeAction) puis utiliser Ctrl+. (point) pour ajouter l’instruction using appropriée pour ce type.

Placez ensuite le point d’insertion de l'éditeur dans l'identificateur ChangeToImmutableArrayEmpty et utilisez Ctrl+. à nouveau afin de générer le stub de méthode.

Ce dernier extrait de code que vous avez ajouté inscrit le correctif de code en transmettant un CodeAction et l’ID de diagnostic pour le type de problème trouvé. Dans cet exemple, il n’existe qu’un seul ID de diagnostic pour lequel ce code fournit des correctifs. Vous pouvez donc simplement passer le premier élément du tableau d’ID de diagnostic. Lorsque vous créez le CodeAction, vous passez le texte que l’interface utilisateur de l’ampoule doit utiliser comme description du correctif de code. Vous transmettez également une fonction qui prend un CancellationToken et renvoie un nouveau document. Le nouveau document a une nouvelle arborescence de syntaxe qui inclut votre code corrigé qui appelle ImmutableArray.Empty. Cet extrait de code utilise une expression lambda afin de pouvoir se fermer sur le nœud objectCreation et le document du contexte.

Construisez la nouvelle arborescence de syntaxe. Dans la méthode ChangeToImmutableArrayEmpty dont vous avez généré précédemment le stub, entrez la ligne de code : ImmutableArray<int>.Empty;. Si vous affichez à nouveau la fenêtre du visualiseur de syntaxe, cette syntaxe est un nœud SimpleMemberAccessExpression. C’est ce que cette méthode doit construire et retourner dans un nouveau document.

La première modification apportée à ChangeToImmutableArrayEmpty consiste à ajouter async avant Task<Document>, car les générateurs de code ne peuvent pas supposer que la méthode doit être asynchrone.

Renseignez le corps avec le code suivant pour que votre méthode ressemble à ce qui suit :

private async Task<Document> ChangeToImmutableArrayEmpty(
    ObjectCreationExpressionSyntax objectCreation, Document document,
    CancellationToken c)
{
    var generator = SyntaxGenerator.GetGenerator(document);
    var memberAccess =
        generator.MemberAccessExpression(objectCreation.Type, "Empty");
    var oldRoot = await document.GetSyntaxRootAsync(c);
    var newRoot = oldRoot.ReplaceNode(objectCreation, memberAccess);
    return document.WithSyntaxRoot(newRoot);
}

Vous devrez placer le point d’insertion de l’éditeur dans l’identificateur de SyntaxGenerator et utiliser Ctrl+. (point) pour ajouter l’instruction using appropriée pour ce type.

Ce code utilise SyntaxGenerator, qui est un type utile pour construire un nouveau code. Après avoir obtenu un générateur pour le document qui a le problème de code, ChangeToImmutableArrayEmpty appelle MemberAccessExpression, en transmettant le type qui contient le membre auquel nous souhaitons accéder et en passant le nom du membre sous forme de chaîne.

Ensuite, la méthode extrait la racine du document et, étant donné que cela peut impliquer un travail arbitraire dans le cas général, le code attend cet appel et transmet le jeton d’annulation. Les modèles de code Roslyn sont immuables, comme l’utilisation d’une chaîne .NET ; lorsque vous mettez à jour la chaîne, vous obtenez un nouvel objet de chaîne en retour. Lorsque vous appelez ReplaceNode, vous récupérez un nouveau nœud racine. La plupart de l’arborescence de syntaxe est partagée (car elle est immuable), mais le nœud objectCreation est remplacé par le nœud memberAccess, ainsi que tous les nœuds parents jusqu’à la racine de l’arborescence de syntaxe.

Essayer votre correctif de code

Vous pouvez maintenant appuyer sur F5 pour exécuter votre analyseur dans une deuxième instance de Visual Studio. Ouvrez le projet de console que vous avez utilisé précédemment. Vous devriez maintenant voir apparaître l’ampoule à l’emplacement de votre nouvelle expression de création d’objet pour ImmutableArray<int>. Si vous appuyez sur Ctrl+. (période), vous verrez alors votre correctif de code et vous verrez un aperçu de différence de code généré automatiquement dans l’interface utilisateur de l’ampoule. Roslyn crée cela pour vous.

Conseil Professionnel : Si vous lancez la deuxième instance de Visual Studio et que vous ne voyez pas l’ampoule avec votre correctif de code, vous devrez peut-être effacer le cache du composant Visual Studio. L’effacement du cache force Visual Studio à réinscrire les composants. Visual Studio doit donc récupérer votre dernier composant. Tout d’abord, arrêtez la deuxième instance de Visual Studio. Ensuite, dans Explorateur Windows, accédez à %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. (La version 16.0 passe de la version à la version avec Visual Studio.) Supprimez le sous-répertoire ComponentModelCache.

Parler vidéo et terminer le projet de code

Vous pouvez voir tout le code terminé ici. Les sous-dossiers DoNotUseImmutableArrayCollectionInitializer et DoNotUseImmutableArrayCtor chacun ont un fichier C# pour rechercher des problèmes et un fichier C# qui implémente les correctifs de code qui s’affichent dans l’interface utilisateur de l’ampoule Visual Studio. Notez que le code terminé comporte un peu plus d'abstraction pour éviter de récupérer à plusieurs reprises l'objet de type ImmutableArray<T>. Il utilise des actions inscrites imbriquées pour enregistrer l’objet de type dans un contexte disponible chaque fois que les sous-actions (analyser la création d’objet et analyser les initialisations de collection) s’exécutent.