Analizzatori Roslyn e libreria compatibile con il 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 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.

Attività iniziali

Per compilare questo esempio, è necessario quanto segue:

  • Visual Studio 2015 (non express edition) o versione successiva. È possibile usare visual Studio Community Edition gratuito
  • Visual Studio SDK. È anche possibile, quando si installa Visual Studio, controllare Visual Studio Extensibility Tools in Common Tools per installare l'SDK contemporaneamente. Se Visual Studio è già stato installato, è anche possibile installare questo SDK passando al menu principale File>nuovo>progetto, scegliendo C# nel riquadro di spostamento a sinistra e scegliendo Estendibilità. Quando si sceglie il modello di progetto di navigazione "Install the Visual Studio Extensibility Tools" (Installa gli strumenti di estendibilità di Visual Studio), viene richiesto di scaricare e installare l'SDK.
  • .NET Compiler Platform ("Roslyn") SDK. È anche possibile installare questo SDK passando al menu principale File>nuovo>progetto, scegliendo C# nel riquadro di spostamento a sinistra e quindi scegliendo Estendibilità. Quando si sceglie il modello di progetto di navigazione "Download the .NET Compiler Platform SDK", viene richiesto 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 di ImmutableArrays e delle tecniche di ottimizzazione usate nell'implementazione, le intuizioni degli sviluppatori C# causano agli utenti della libreria di scrivere codice interrotto, come illustrato di seguito. Inoltre, gli utenti non visualizzano gli errori fino alla fase di esecuzione, che non è l'esperienza di qualità usata per 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);

La creazione di matrici vuote da compilare con righe di codice successive e l'uso della sintassi dell'inizializzatore di raccolta hanno familiarità con gli sviluppatori C#. Tuttavia, la scrittura dello stesso codice per un errore ImmutableArray si arresta in modo anomalo 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. Gli struct devono avere costruttori senza parametri in modo che default(T) le espressioni possano restituire struct con tutti i membri 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 ImmutableArray.Add metodo 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 valore predefinito ImmutableArray. La chiamata a Length si arresta in modo anomalo 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 compilare l'analizzatore, prima di tutto capire il tipo di SyntaxNode da cercare. Avviare il visualizzatore di sintassi dal menu Visualizza>altro visualizzatore sintassi Roslyn di Windows>.

Posizionare il cursore dell'editor nella riga che dichiara b1. Si noterà che il visualizzatore di sintassi mostra che ci si trova in un LocalDeclarationStatement nodo dell'albero della sintassi. Questo nodo ha un VariableDeclarationoggetto , che a sua volta ha un oggetto , che a sua volta ha un VariableDeclaratorEqualsValueClauseoggetto e infine un oggetto ObjectCreationExpression. Quando si fa clic sull'albero del visualizzatore di sintassi dei nodi, la sintassi nella finestra dell'editor evidenzia per visualizzare il codice rappresentato da tale nodo. I nomi dei tipi secondari SyntaxNode corrispondono ai nomi usati nella grammatica C#.

Creare il progetto analizzatore

Scegliere File>Nuovo>progetto dal menu principale. Nella finestra di dialogo Nuovo progetto, in Progetti C# nella barra di spostamento a sinistra scegliere Estendibilità e nel riquadro destro scegliere il modello di progetto Analizzatore 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 nuova classe ha un DiagnosticAnalyzerAttribute analizzatore dichiarante che è rilevante per il linguaggio C# in modo che il compilatore rilevi e carichi 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 DiagnosticAnalyzer classe per visualizzare il Initialize metodo . Il compilatore chiama questo metodo quando si attiva un analizzatore. Il metodo accetta un AnalysisContext oggetto 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) {}

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

Come un altro esempio, RegisterCompilationStartAction, richiama il codice all'inizio di una compilazione, utile quando è necessario 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 viene richiamato a causa della fine della compilazione, è possibile analizzare tutti i percorsi salvati, ad esempio, per segnalare i simboli usati dal codice da ogni using istruzione.

Usando il visualizzatore di sintassi, si è appreso che si vuole chiamare quando il compilatore elabora un oggetto ObjectCreationExpression. Usare 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 dell'analizzatore usano un'espressione lambda durante la registrazione delle azioni, che consente di mantenere gli analizzatori senza stato. È possibile usare la funzionalità Genera dall'utilizzo di Visual Studio per creare il AnalyzeObjectCreation metodo . Questo genera anche il tipo corretto di parametro di contesto.

Impostare le proprietà per gli utenti dell'analizzatore

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

internal const string Category = "Naming";

Cambia "Naming" in "API Guidance".

Trovare e aprire quindi il file Resources.resx nel progetto usando il Esplora soluzioni. È possibile inserire una descrizione per l'analizzatore, il titolo e così via. Per il momento è possibile modificare il valore per tutti questi valori "Don't use ImmutableArray<T> constructor" . È possibile inserire argomenti di formattazione delle stringhe nella stringa ({0}, e {1}così via) e versioni successive quando si chiama Diagnostic.Create(), è possibile fornire una params matrice di argomenti da passare.

Analizzare un'espressione di creazione di oggetti

Il AnalyzeObjectCreation metodo accetta un tipo diverso di contesto fornito dal framework dell'analizzatore del codice. Il Initialize metodo consente di registrare i callback delle azioni per configurare l'analizzatore AnalysisContext . L'oggetto SyntaxNodeAnalysisContext, ad esempio, ha un oggetto 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.

Ottenere il nodo, che si può presupporre è il tipo per cui è stata filtrata l'azione del nodo della sintassi:

var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

Avviare Visual Studio con l'analizzatore la prima volta

Avviare Visual Studio compilando ed eseguendo l'analizzatore (premere F5). Poiché il progetto di avvio nel Esplora soluzioni è il progetto VSIX, l'esecuzione del codice compila il codice e un VSIX e quindi avvia Visual Studio con tale VSIX installato. Quando si avvia Visual Studio in questo modo, viene avviato con un hive del Registro di sistema distinto in modo che l'uso principale di Visual Studio non sia interessato dalle istanze di test durante la compilazione di 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 sottolineatura ondulata perché è necessario ottenere il pacchetto NuGet non modificabile e aggiungere un'istruzione using al codice. Premere il pulsante destro del puntatore sul nodo del progetto nel Esplora soluzioni e scegliere Gestisci 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.

Viene comunque visualizzata una sottolineatura ImmutableArrayrossa sotto , quindi posizionare il cursore in tale identificatore e premere CTRL+. (punto) per visualizzare il menu di correzione suggerito e scegliere di aggiungere l'istruzione appropriata.using

Salvare tutto e chiudere la seconda istanza di Visual Studio per il momento per inserire lo stato pulito per continuare.

Completare l'analizzatore usando la modifica e continuare

Nella prima istanza di Visual Studio impostare un punto di interruzione all'inizio del AnalyzeObjectCreation metodo premendo F9 con il cursore sulla prima riga.

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

Tornare alla prima istanza di Visual Studio nel punto di interruzione perché il compilatore Roslyn ha visto un'espressione di creazione dell'oggetto e ha chiamato nell'analizzatore.

Ottenere il nodo di creazione dell'oggetto. Scorrere la riga che imposta la objectCreation variabile premendo F10 e nella finestra immediata 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 l'oggetto Tipo T> ImmutableArray<. È necessario verificare se il tipo creato è ImmutableArray. In primo luogo, si ottiene l'oggetto che rappresenta questo tipo. È possibile controllare i tipi usando il modello semantico per assicurarsi di avere esattamente il tipo corretto e di non confrontare la stringa da 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 objectCreation variabile e passare sopra la 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 dell'oggetto per verificare se si tratta del tipo ImmutableArray<T> . Usare di nuovo il modello semantico per ottenere informazioni sui simboli per il 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, è presente un'istruzione mancante using ), è necessario verificare di symbolInfo essere null. Per completare l'analisi, è necessario ottenere un tipo denominato (INamedTypeSymbol) dall'oggetto informazioni sui simboli.

Confrontare 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 confrontare il risultato 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. Usare la regola creata automaticamente nel modello di progetto, definita prima del metodo Initialize. Poiché questa situazione nel codice è un errore, è possibile modificare la riga inizializzata regola in modo da sostituire DiagnosticSeverity.Warning (ondulata verde) con DiagnosticSeverity.Error (sottolineatura ondulata rossa). Il resto della regola inizializza dalle risorse modificate all'inizio della procedura dettagliata. È anche necessario segnalare la posizione per la sottolineatura ondulata, ovvero la posizione della specifica del tipo dell'espressione di creazione dell'oggetto. Immettere questo codice nel if blocco :

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 che sia possibile visualizzare il funzionamento dell'analizzatore e interrompere la restituzione alla prima istanza di Visual Studio. Trascinare il puntatore di esecuzione all'inizio del metodo e premere 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 in ImmutableType<int>.

Aggiunta di una "correzione del codice" 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. Usare il menu di scelta rapida (pulsante del puntatore a destra) nel nodo del progetto nella Esplora soluzioni e scegliere di aggiungere un nuovo elemento. Aggiungere una classe denominata BuildCodeFixProvider. Questa classe deve derivare da CodeFixProvidere sarà necessario usare CTRL+. (punto) per richiamare la correzione del codice che aggiunge l'istruzione corretta.using Questa classe deve anche essere annotata con ExportCodeFixProvider l'attributo 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 e premere CTRL+. (punto) per stubare l'implementazione CodeFixProvider per questa classe di base astratta. In questo modo viene generata una proprietà e un metodo.

Implementare la proprietà . Compilare il FixableDiagnosticIds corpo della get proprietà con il codice seguente:

return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);

Roslyn riunisce la diagnostica e le correzioni associando questi identificatori, che sono solo stringhe. Il modello di progetto ha generato automaticamente un ID di diagnostica e si è liberi di modificarlo. Il codice nella proprietà restituisce semplicemente l'ID dalla 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. Se si digita "context" nel corpo del metodo, l'elenco di completamento di IntelliSense mostrerà alcuni membri utili. È presente un membro CancellationToken che è possibile verificare se un elemento vuole annullare la correzione. È presente un membro document che ha 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 del percorso del codice specificato quando è stata segnalata la diagnostica.

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

Ottenere 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 dal Document 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 deve essere reattiva durante questo periodo, che usa async 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. Si passa l'intervallo del contesto, ma il nodo che si trova potrebbe non essere il codice che è necessario modificare. La diagnostica segnalata ha fornito solo l'intervallo per l'identificatore di tipo (dove appartiene l'interruttore), ma è necessario sostituire l'intera espressione di creazione dell'oggetto, inclusa la new parola chiave all'inizio e le parentesi alla fine. Aggiungere il codice seguente al metodo (e usare 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+. (punto) quando l'analizzatore si alterna a un costruttore non valido.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 RegisterCodeFixAsync metodo:

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 , CodeActionquindi usare CTRL+. (punto) per aggiungere l'istruzione appropriata using per questo tipo.

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

Questo ultimo frammento di codice aggiunto registra la correzione del codice passando un CodeAction e l'ID di diagnostica 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 si crea CodeAction, si passa il testo che l'interfaccia utente della lampadina deve usare come descrizione della correzione del codice. Si passa anche una funzione che accetta cancellationToken e restituisce un nuovo documento. Il nuovo documento include una nuova struttura ad albero della sintassi che include il codice con patch 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 il ChangeToImmutableArrayEmpty cui stub generato in precedenza immettere la riga di codice: ImmutableArray<int>.Empty;. Se si visualizza di nuovo la finestra degli strumenti del visualizzatore di sintassi, è possibile notare 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 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 SyntaxGenerator e usare CTRL+. (punto) per aggiungere l'istruzione appropriata using per questo tipo.

Questo codice usa SyntaxGenerator, che è un tipo utile per costruire un nuovo codice. Dopo aver ottenuto un generatore per il documento con il problema di codice, ChangeToImmutableArrayEmpty chiama MemberAccessExpression, passando il tipo con il membro a cui si vuole accedere e passando il nome del membro come stringa.

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 objectCreation nodo viene sostituito con il memberAccess nodo, nonché tutti i nodi padre fino alla radice dell'albero della sintassi.

Provare la correzione del codice

È ora possibile premere F5 per eseguire l'analizzatore in una seconda istanza di Visual Studio. Aprire il progetto console usato in precedenza. Verrà ora visualizzata la lampadina in cui si trova la nuova espressione di creazione dell'oggetto per ImmutableArray<int>. Se si preme CTRL+. (punto), verrà visualizzata la correzione del codice e verrà visualizzata 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 non viene visualizzata la lampadina 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 di tutto, arrestare la seconda istanza di Visual Studio. Quindi, in Esplora risorse passare a %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. La versione "16.0" passa dalla versione alla versione con Visual Studio. Eliminare la sottodirectory ComponentModelCache.

Video di conversazione e completamento del progetto di codice

Qui è possibile visualizzare tutto il codice completato. Le sottocartelle DoNotUseImmutableArrayCollectionInitializer e DoNotUseImmutableArrayCtor hanno un file C# per trovare problemi e un file C# che implementa le correzioni di codice visualizzate nell'interfaccia utente della lampadina di Visual Studio. Si noti che il codice finito ha un'astrazione leggermente maggiore per evitare di recuperare l'oggetto di tipo ImmutableArray<T> su e oltre. 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).