Condividi tramite


Analizzatori Roslyn e libreria sensibile al codice per ImmutableArrays

La piattaforma del compilatore .NET ("Roslyn") consente di creare librerie con riconoscimento del codice. Una libreria compatibile con il codice offre funzionalità che è possibile usare e strumenti (analizzatori Roslyn) per facilitare l'uso della libreria nel modo migliore o per evitare errori. Questo argomento illustra come creare un analizzatore Roslyn del mondo reale per rilevare errori comuni quando si usa il pacchetto NuGet System.Collections.Immutable . L'esempio illustra anche come fornire una correzione del codice per un problema di codice rilevato dall'analizzatore. Gli utenti visualizzano correzioni di codice nell'interfaccia utente della lampadina di Visual Studio e possono applicare automaticamente una correzione per il codice.

Inizia

Per compilare questo esempio, è necessario quanto segue:

  • Visual Studio 2015 (non un'edizione Express) o versione successiva. Puoi usare l'edizione gratuita di Visual Studio Community Edition
  • Visual Studio SDK. Quando si installa Visual Studio, è anche possibile controllare Visual Studio Extensibility Tools in Common Tools per installare l'SDK contemporaneamente. Se Visual Studio è già installato, è anche possibile installare questo SDK passando al menu principale File>Nuovo>Progetto, scegliendo C# nel pannello di navigazione a sinistra e quindi scegliendo Estendibilità. Quando si sceglie il modello di progetto "Install the Visual Studio Extensibility Tools" breadcrumb (Installazione degli strumenti di estendibilità di Visual Studio" ), viene richiesto di scaricare e installare l'SDK.
  • .NET Compiler Platform ("Roslyn") SDK. È anche possibile installare questo SDK accedendo al menu principale File>Nuovo>Progetto, scegliendo C# nel pannello di navigazione a sinistra e quindi scegliendo Estensibilità. Quando selezioni il modello di progetto "Scaricare .NET Compiler Platform SDK" nel navigatore, ti viene chiesto di scaricare e installare l'SDK. Questo SDK include il visualizzatore della sintassi Roslyn . Questo strumento utile consente di individuare i tipi di modello di codice da cercare nell'analizzatore. L'infrastruttura dell'analizzatore chiama il codice per tipi di modello di codice specifici, quindi il codice viene eseguito solo quando necessario e può concentrarsi solo sull'analisi del codice pertinente.

Qual è il problema?

Si supponga di fornire una libreria con supporto ImmutableArray (ad esempio, System.Collections.Immutable.ImmutableArray<T>). Gli sviluppatori C# hanno molta esperienza con le matrici .NET. Tuttavia, a causa della natura degli ImmutableArrays e delle tecniche di ottimizzazione usate nell'implementazione, le intuizioni degli sviluppatori C# inducono gli utenti della tua libreria a scrivere codice errato, come spiegato di seguito. Inoltre, gli utenti non visualizzano gli errori fino alla fase di esecuzione, il che non corrisponde all'esperienza di qualità a cui sono abituati in Visual Studio con .NET.

Gli utenti hanno familiarità con la scrittura di codice simile al seguente:

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

Creare array vuoti da riempire con righe di codice successive e utilizzare la sintassi dell'inizializzatore di raccolta sono pratiche familiari agli sviluppatori C#. Tuttavia, scrivere lo stesso codice per un ImmutableArray provoca un crash in fase di esecuzione.

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

Il primo errore è dovuto all'implementazione immutableArray che usa uno struct per eseguire il wrapping dell'archiviazione dei dati sottostante. Le strutture devono avere costruttori senza parametri affinché le espressioni default(T) possano restituire strutture con tutti i membri a zero o Null. Quando il codice accede a b1.Length, si verifica un errore di dereferenziazione null in fase di esecuzione perché non esiste una matrice di archiviazione sottostante nello struct ImmutableArray. Il modo corretto per creare un oggetto ImmutableArray vuoto è ImmutableArray<int>.Empty.

L'errore con gli inizializzatori di raccolta si verifica perché il metodo ImmutableArray.Add restituisce nuove istanze ogni volta che viene chiamata. Poiché ImmutableArrays non cambia mai, quando si aggiunge un nuovo elemento, viene restituito un nuovo oggetto ImmutableArray , che può condividere l'archiviazione per motivi di prestazioni con un oggetto ImmutableArray esistente in precedenza. Poiché b2 punta al primo ImmutableArray prima di chiamare Add() cinque volte, b2 è un ImmutableArray predefinito. La chiamata a Length su di esso va in crash anche con un errore di dereferenziazione null. Il modo corretto per inizializzare un oggetto ImmutableArray senza chiamare manualmente Add consiste nell'usare ImmutableArray.CreateRange(new int[] {1, 2, 3, 4, 5}).

Trovare i tipi di nodo della sintassi pertinenti per attivare l'analizzatore

Per iniziare a costruire l'analizzatore, prima di tutto capire quale tipo di nodo sintattico devi cercare. Avviare la del visualizzatore di sintassi dal menu VisualizzaAltro visualizzatore della sintassi roslyn di Windows.

Posizionare il cursore dell'editor sulla riga che dichiara b1. Si noterà che il visualizzatore di sintassi mostra che ci si trova in un nodo LocalDeclarationStatement dell'albero della sintassi. Questo nodo ha un VariableDeclaration, che a sua volta ha un VariableDeclarator, che a sua volta ha un EqualsValueClausee infine è presente un ObjectCreationExpression. Quando si fa clic nell'albero dei nodi del visualizzatore di sintassi, la sintassi nella finestra dell'editor viene evidenziata per mostrare il codice rappresentato da quel nodo. I nomi dei tipi secondari SyntaxNode corrispondono ai nomi usati nella grammatica C#.

Creare il progetto analizzatore

Scegliere File>Nuovo>Progettodal menu principale. Nella finestra di dialogo Nuovo Progetto, in progetti C# nella barra di scorrimento a sinistra, scegliere Estendibilitàe nel riquadro destro scegliere il modello di progetto Analyzer con Correzione del Codice. Immettere un nome e confermare la finestra di dialogo.

Il modello apre un file DiagnosticAnalyzer.cs. Scegliere la scheda buffer dell'editor. Questo file ha una classe analizzatore (formata dal nome assegnato al progetto) che deriva da DiagnosticAnalyzer (un tipo di API Roslyn). La tua nuova classe ha una DiagnosticAnalyzerAttribute che dichiara che l'analizzatore è rilevante per il linguaggio C#, in modo tale che il compilatore possa scoprire e caricare l'analizzatore.

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

È possibile implementare un analizzatore usando Visual Basic destinato al codice C# e viceversa. È più importante in DiagnosticAnalyzerAttribute scegliere se l'analizzatore è destinato a un linguaggio o a entrambi. Gli analizzatori più sofisticati che richiedono una modellazione dettagliata del linguaggio possono essere destinati solo a una singola lingua. Se l'analizzatore, ad esempio, controlla solo i nomi dei tipi o i nomi dei membri pubblici, può essere possibile usare il modello di linguaggio comune offerto da Roslyn in Visual Basic e C#. Ad esempio, FxCop avvisa che una classe implementa ISerializable, ma la classe non dispone dell'attributo SerializableAttribute è indipendente dal linguaggio e funziona sia per il codice Visual Basic che per il codice C#.

Inizializzare l'analizzatore

Scorrere verso il basso un po' nella classe DiagnosticAnalyzer per visualizzare il metodo Initialize. Il compilatore chiama questo metodo quando si attiva un analizzatore. Il metodo accetta un oggetto AnalysisContext che consente all'analizzatore di ottenere informazioni sul contesto e di registrare i callback per gli eventi per i tipi di codice da analizzare.

public override void Initialize(AnalysisContext context) {}

Apri una nuova riga in questo metodo e digita "context." per visualizzare l'elenco di completamento IntelliSense. Nell'elenco di completamento, troverai molti metodi Register... disponibili per gestire vari tipi di eventi. Ad esempio, il primo, RegisterCodeBlockAction, fa riferimento al tuo codice per un blocco, che di solito è codice tra parentesi graffe. La registrazione di un blocco richiama anche il tuo codice per l'inizializzatore di un campo, il valore assegnato a un attributo o il valore di un parametro facoltativo.

Un altro esempio è il RegisterCompilationStartAction, che invoca il tuo codice all'inizio di una compilazione ed è utile se devi raccogliere lo stato in più posizioni. È possibile creare una struttura di dati, ad esempio, per raccogliere tutti i simboli usati e ogni volta che l'analizzatore viene richiamato per una sintassi o un simbolo, è possibile salvare informazioni su ogni posizione nella struttura dei dati. Quando sei richiamato a causa della fine della compilazione, puoi analizzare tutte le posizioni che hai salvato, ad esempio, per riportare quali simboli il codice utilizza da ogni istruzione using.

Usando il Visualizzatore di Sintassi , hai appreso che vuoi essere chiamato quando il compilatore elabora un ObjectCreationExpression. Usi questo codice per configurare il callback.

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

Si esegue la registrazione per un nodo di sintassi e si filtra solo per i nodi della sintassi di creazione di oggetti. Per convenzione, gli autori degli analizzatori utilizzano un'espressione lambda durante la registrazione delle azioni, il che permette di mantenere gli analizzatori senza stato. È possibile usare in Visual Studio la funzionalità Genera dall'uso per creare il metodo AnalyzeObjectCreation. Questo genera anche il tipo corretto di parametro di contesto.

Impostare le proprietà per gli utenti dell'analizzatore

Per fare in modo che l'analizzatore venga visualizzato correttamente nell'interfaccia utente di Visual Studio, cercare e modificare la seguente riga di codice per identificare l'analizzatore:

internal const string Category = "Naming";

Cambia "Naming" in "API Guidance".

Quindi trova e apri il file Resources.resx nel tuo progetto usando Solution Explorer. È possibile inserire una descrizione per l'analizzatore, il titolo e così via. È possibile modificare il valore per tutti a "Don't use ImmutableArray<T> constructor" per adesso. È possibile inserire argomenti di formattazione delle stringhe nella stringa ({0}, {1}e così via), e in seguito, quando si chiama Diagnostic.Create(), è possibile fornire un array di argomenti params da passare.

Analizzare un'espressione di creazione di oggetti

Il metodo AnalyzeObjectCreation accetta un tipo diverso di contesto fornito dal framework dell'analizzatore del codice. Il AnalysisContext del metodo Initialize consente di registrare i callback delle azioni per configurare l'analizzatore. L'SyntaxNodeAnalysisContext, ad esempio, ha un CancellationToken che è possibile passare. Se un utente inizia a digitare nell'editor, Roslyn annulla gli analizzatori in esecuzione per salvare il lavoro e migliorare le prestazioni. Come altro esempio, questo contesto ha una proprietà Node che restituisce il nodo della sintassi di creazione dell'oggetto.

Ottieni il nodo, che puoi presumere sia il tipo per il quale hai filtrato l'azione del nodo della sintassi.

var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

Avvia Visual Studio con l'analizzatore per la prima volta

Avviare Visual Studio compilando ed eseguendo l'analizzatore (premere F5). Poiché il progetto di avvio in Solution Explorer è il progetto VSIX, l'esecuzione del codice consente di compilarlo e creare un pacchetto VSIX, quindi avvia Visual Studio con il VSIX installato. Quando si avvia Visual Studio in questo modo, viene avviato con una chiave di registro distinta, in modo che l'utilizzo principale di Visual Studio non venga influenzato dalle istanze di test durante lo sviluppo degli analizzatori. La prima volta che si avvia in questo modo, Visual Studio esegue diverse inizializzazioni simili a quando è stato avviato Visual Studio per la prima volta dopo l'installazione.

Creare un progetto console e quindi immettere il codice della matrice nel metodo Main delle applicazioni 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);

Le righe di codice con ImmutableArray hanno squiggles perché è necessario ottenere il pacchetto NuGet non modificabile e aggiungere un'istruzione using al codice. Fare clic con il tasto destro del mouse sul nodo del progetto nell' Esplora soluzioni e scegliere Gestire i pacchetti NuGet. Nel gestore NuGet digitare "Immutable" nella casella di ricerca e scegliere l'elemento System.Collections.Immutable (non scegliere Microsoft.Bcl.Immutable) nel riquadro sinistro e premere il pulsante Installa nel riquadro destro. L'installazione del pacchetto aggiunge un riferimento ai riferimenti al progetto.

Sotto ImmutableArrayvengono comunque visualizzati gli squiggli rossi, quindi posizionare il cursore in tale identificatore e premere CTRL+. (punto) per visualizzare il menu di correzione suggerito e scegliere di aggiungere l'istruzione using appropriata.

Salva tutto e chiudi la seconda istanza di Visual Studio per ora, per restare in uno stato pulito e continuare.

Finire l'analizzatore utilizzando modifica e continua

Nella prima istanza di Visual Studio, imposta un punto di interruzione all'inizio del metodo AnalyzeObjectCreation, posizionando il cursore sulla prima riga e premendo F9.

Avviare di nuovo l'analizzatore con F5e nella seconda istanza di Visual Studio riaprire l'applicazione console creata l'ultima volta.

Torni alla prima istanza di Visual Studio al punto di interruzione perché il compilatore Roslyn ha visto un'espressione di creazione di oggetti e ha invocato il tuo analizzatore.

Ottieni il nodo di creazione dell'oggetto. Passare sopra la riga che imposta la variabile objectCreation premendo F10, e nella finestra Immediate valutare l'espressione "objectCreation.ToString()". Si noterà che il nodo della sintassi a cui punta la variabile è il codice "new ImmutableArray<int>()", proprio quello che si sta cercando.

Ottiene un oggetto di tipo ImmutableArray<T>. È necessario verificare se il tipo creato è ImmutableArray. In primo luogo, si ottiene l'oggetto che rappresenta questo tipo. Controlla i tipi usando il modello semantico per assicurarti di avere esattamente il tipo corretto e di non confrontare la stringa con ToString(). Immettere la riga di codice seguente alla fine della funzione:

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

I tipi generici vengono designati nei metadati con backticks (') e il numero di parametri generici. Ecco perché non vedi "... ImmutableArray<T>" nel nome dei metadati.

Il modello semantico include molti elementi utili su di esso che consentono di porre domande su simboli, flusso di dati, durata variabile e così via. Roslyn separa i nodi della sintassi dal modello semantico per vari motivi di progettazione (prestazioni, modellazione di codice errato e così via). Si vuole che il modello di compilazione cerchi le informazioni contenute nei riferimenti per un confronto accurato.

È possibile trascinare il puntatore di esecuzione giallo sul lato sinistro della finestra dell'editor. Trascinarlo fino alla riga che imposta la variabile objectCreation e passare alla nuova riga di codice usando F10. Se si passa il puntatore del mouse sulla variabile immutableArrayOfType, si noterà che è stato trovato il tipo esatto nel modello semantico.

Ottiene il tipo dell'espressione di creazione dell'oggetto. "Type" viene usato in alcuni modi in questo articolo, ma questo significa che se si ha "nuova espressione Foo", è necessario ottenere un modello di Foo. È necessario ottenere il tipo dell'espressione di creazione di oggetti per verificare se è di tipo ImmutableArray<T>. Utilizza nuovamente il modello semantico per ottenere le informazioni sul simbolo di tipo (ImmutableArray) nell'espressione di creazione dell'oggetto. Immettere la riga di codice seguente alla fine della funzione:

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

Poiché l'analizzatore deve gestire codice incompleto o non corretto nei buffer dell'editor (ad esempio, se manca un'istruzione using), è necessario verificare che symbolInfo sia null. Per completare l'analisi, è necessario ottenere un tipo denominato (INamedTypeSymbol) dall'oggetto informazioni sui simboli.

Confronta i tipi. Poiché esiste un tipo generico aperto di T che si sta cercando e il tipo nel codice è un tipo generico concreto, si eseguono query sulle informazioni sul simbolo per individuare il tipo costruito da (un tipo generico aperto) e confrontarlo con immutableArrayOfTType. Immettere quanto segue alla fine del metodo :

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

Segnalare la diagnostica. La segnalazione della diagnostica è piuttosto semplice. Si usa la regola creata per te nel modello di progetto, definita prima del metodo Initialize. Poiché questa situazione nel codice è un errore, è possibile modificare la riga che ha inizializzato Rule per sostituire DiagnosticSeverity.Warning (ondulato verde) con DiagnosticSeverity.Error (ondulato rosso). Il resto della regola viene inizializzato dalle risorse che hai modificato all'inizio del walkthrough. È necessario segnalare anche la posizione della sottolineatura ondulata, ovvero il punto in cui viene specificato il tipo nell'espressione di creazione dell'oggetto. Immettere questo codice nel blocco if:

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

La funzione dovrebbe essere simile alla seguente (ad esempio formattata in modo diverso):

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

Rimuovere il punto di interruzione in modo da poter vedere il funzionamento dell'analizzatore e per evitare di tornare alla prima istanza di Visual Studio. Trascina il puntatore di esecuzione verso l'inizio del tuo metodo e premi F5 per continuare l'esecuzione. Quando si torna alla seconda istanza di Visual Studio, il compilatore inizierà a esaminare nuovamente il codice e chiamerà l'analizzatore. È possibile visualizzare una sottolineatura ondulata sotto ImmutableType<int>.

Aggiunta di una correzione per il problema del codice

Prima di iniziare, chiudere la seconda istanza di Visual Studio e arrestare il debug nella prima istanza di Visual Studio (in cui si sta sviluppando l'analizzatore).

Aggiungere una nuova classe. Usa il menu di scelta rapida (pulsante destro del puntatore) sul nodo del progetto in Esplora soluzioni e scegli di aggiungere un nuovo elemento. Aggiungere una classe denominata BuildCodeFixProvider. Questa classe deve derivare da CodeFixProvidered è necessario usare CTRL+. (punto) per richiamare la correzione del codice che aggiunge l'istruzione using corretta. Anche questa classe deve essere annotata con l'attributo ExportCodeFixProvider e sarà necessario aggiungere un'istruzione using per risolvere l'enumerazione LanguageNames. È necessario disporre di un file di classe con il codice seguente:

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

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

Stub out membri derivati. Posizionare ora il cursore dell'editor nell'identificatore CodeFixProvider e premere Ctrl+. (punto) per creare un'implementazione per questa classe base astratta. In questo modo vengono generati una proprietà e un metodo.

Implementa la proprietà. Compila il corpo di get della proprietà FixableDiagnosticIds con il codice seguente:

return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);

Roslyn integra la diagnostica e le correzioni associando questi identificatori, che sono solo stringhe di testo. Il modello di progetto ha generato un ID diagnostico, che puoi modificare liberamente. Il codice nella proprietà restituisce semplicemente l'ID della classe analizzatore.

Il metodo RegisterCodeFixAsync accetta un contesto. Il contesto è importante perché una correzione del codice può essere applicata a più diagnostica o potrebbe verificarsi più di un problema in una riga di codice. Digitando "context." nel corpo del metodo, l'elenco di completamento di IntelliSense mostrerà alcuni membri utili. Esiste un membro CancellationToken che è possibile controllare per vedere se qualcosa desidera annullare la correzione. È presente un membro del documento che possiede molti membri utili e consente di accedere agli oggetti modello di progetto e soluzione. Esiste un membro Span che rappresenta l'inizio e la fine della posizione del codice specificata quando è stato segnalato il problema diagnostico.

Rendere il metodo asincrono. La prima cosa da fare è correggere la dichiarazione del metodo generato come metodo async. La correzione del codice per creare uno stub dell'implementazione di una classe astratta non include la parola chiave async anche se il metodo restituisce un Task.

Ottieni la radice dell'albero della sintassi. Per modificare il codice è necessario produrre un nuovo albero della sintassi con le modifiche apportate dalla correzione del codice. È necessario il Document dal contesto per chiamare GetSyntaxRootAsync. Si tratta di un metodo asincrono perché esiste un lavoro sconosciuto per ottenere l'albero della sintassi, incluso il recupero del file dal disco, l'analisi e la compilazione del modello di codice Roslyn. L'interfaccia utente di Visual Studio dovrebbe essere reattiva durante questo periodo, e l'uso di async lo abilita. Sostituire la riga di codice nel metodo con quanto segue:

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

Trovare il nodo con il problema. Passi l'intervallo del contesto, ma il nodo che individui potrebbe non essere il codice che devi modificare. La diagnostica segnalata ha fornito solo l'intervallo per l'identificatore di tipo (dove appartiene l'ondulato), ma è necessario sostituire l'intera espressione di creazione dell'oggetto, inclusa la parola chiave new all'inizio e le parentesi alla fine. Aggiungi il seguente codice al tuo metodo (e usa Ctrl+. per aggiungere un'istruzione using per ObjectCreationExpressionSyntax):

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

Registrare la correzione del codice per l'interfaccia utente della lampadina. Quando si registra la correzione del codice, Roslyn si collega automaticamente all'interfaccia utente della lampadina di Visual Studio. Gli utenti finali vedranno che possono usare CTRL+. (periodo) quando l'analizzatore sottolinea un uso non valido del costruttore di ImmutableArray<T>. Poiché il provider di correzione del codice viene eseguito solo quando si verifica un problema, è possibile presupporre che sia presente l'espressione di creazione dell'oggetto che si sta cercando. Dal parametro di contesto è possibile registrare la nuova correzione del codice aggiungendo il codice seguente alla fine del metodo RegisterCodeFixAsync:

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

È necessario inserire il cursore dell'editor nell'identificatore, CodeAction, quindi usare Ctrl+. (punto) per aggiungere la dichiarazione using appropriata per questo tipo.

Posizionare quindi il cursore dell'editor nell'identificatore di ChangeToImmutableArrayEmpty e usare CTRL+. di nuovo per generare automaticamente lo stub del metodo.

Questo ultimo frammento di codice che hai aggiunto registra la correzione del codice trasmettendo un CodeAction e l'ID diagnostico per il tipo di problema rilevato. In questo esempio è presente un solo ID di diagnostica per cui questo codice fornisce correzioni, quindi è sufficiente passare il primo elemento della matrice di ID di diagnostica. Quando crei il CodeAction, inserisci il testo che l'interfaccia utente della lampadina deve usare come descrizione della correzione del codice. Si passa anche una funzione che accetta un TokenDiCancellazione e restituisce un nuovo Documento. Il nuovo documento ha un nuovo albero della sintassi che include il tuo codice aggiornato che chiama ImmutableArray.Empty. Questo frammento di codice usa un'espressione lambda in modo che possa chiudersi sul nodo objectCreation e sul documento del contesto.

Costruire la nuova struttura ad albero della sintassi. Nel metodo ChangeToImmutableArrayEmpty, il cui stub è stato generato in precedenza, immettere la riga di codice: ImmutableArray<int>.Empty;. Se visualizzi di nuovo la finestra degli strumenti Visualizzatore di Sintassi, è possibile vedere che questa sintassi è un nodo SimpleMemberAccessExpression. Questo è ciò che questo metodo deve costruire e restituire in un nuovo documento.

La prima modifica a ChangeToImmutableArrayEmpty consiste nell'aggiungere async prima di Task<Document> perché i generatori di codice non possono presupporre che il metodo sia asincrono.

Compilare il corpo con il codice seguente in modo che il metodo abbia un aspetto simile al seguente:

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

È necessario inserire il cursore dell'editor nell'identificatore di SyntaxGenerator e usare CTRL+. (punto) per aggiungere l'istruzione using appropriata per questo tipo.

Questo codice usa SyntaxGenerator, che è un tipo utile per la creazione di nuovo codice. Dopo aver ottenuto un generatore per il documento con il problema di codice, ChangeToImmutableArrayEmpty chiama MemberAccessExpression, passando il tipo che contiene il membro cui vogliamo accedere e il nome del membro come stringa di testo.

Successivamente, il metodo recupera la radice del documento e, poiché ciò può comportare operazioni arbitrarie nel caso generale, il codice attende questa chiamata e passa il token di annullamento. I modelli di codice Roslyn non sono modificabili, ad esempio l'uso di una stringa .NET; quando si aggiorna la stringa, viene restituito un nuovo oggetto stringa. Quando si chiama ReplaceNode, viene restituito un nuovo nodo radice. La maggior parte dell'albero della sintassi è condivisa (perché non modificabile), ma il nodo objectCreation viene sostituito con il nodo memberAccess, nonché con tutti i nodi padre fino alla radice dell'albero della sintassi.

Prova la tua correzione del codice

È ora possibile premere F5 per eseguire l'analizzatore in una seconda istanza di Visual Studio. Apri il progetto console usato in precedenza. Ora dovresti vedere la lampadina apparire dove si trova la nuova espressione di creazione dell'oggetto per ImmutableArray<int>. Se si preme CTRL+. (periodo), vedrai la correzione del codice e vedrai un'anteprima della differenza di codice generata automaticamente nell'interfaccia utente della lampadina. Roslyn crea questo per te.

Suggerimento pro: Se si avvia la seconda istanza di Visual Studio e la lampadina non viene visualizzata con la correzione del codice, potrebbe essere necessario cancellare la cache dei componenti di Visual Studio. La cancellazione della cache impone a Visual Studio di esaminare nuovamente i componenti, quindi Visual Studio deve quindi selezionare il componente più recente. Prima, chiudere la seconda istanza di Visual Studio. Quindi, in Esplora File, passare a %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. La versione "16.0" varia da una versione all'altra di Visual Studio. Eliminare la sottodirectory ComponentModelCache.

Discussione video e conclusione del progetto di programmazione

È possibile visualizzare tutto il codice finito qui. Le sottocartelle DoNotUseImmutableArrayCollectionInitializer e DoNotUseImmutableArrayCtor hanno un file C# per trovare problemi e un file C# che implementa le correzioni del codice visualizzate nell'interfaccia utente della lampadina di Visual Studio. Si noti che il codice finito ha un livello maggiore di astrazione per evitare di recuperare ripetutamente l'oggetto di tipo ImmutableArray<T>. Usa azioni registrate annidate per salvare l'oggetto tipo in un contesto disponibile ogni volta che vengono eseguite le azioni secondarie (analizzare la creazione di oggetti e analizzare le inizializzazioni della raccolta).