Condividi tramite


Introduzione all'analisi semantica

Questa esercitazione presuppone che si abbia familiarità con l'API Sintassi. L'articolo Introduzione all'analisi della sintassi fornisce un'introduzione sufficiente.

In questa esercitazione verranno esaminate le API Symbol and Binding. Queste API forniscono informazioni sul significato semantico di un programma. Consentono di porre e rispondere a domande sui tipi rappresentati da qualsiasi simbolo nel programma.

È necessario installare .NET Compiler Platform SDK:

Istruzioni di installazione - Programma di installazione di Visual Studio

Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di Visual Studio:

Eseguire l'installazione con il programma di installazione di Visual Studio - Visualizzazione Carichi di lavoro

Il .NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro per lo sviluppo di estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.

  1. Eseguire il programma di installazione di Visual Studio
  2. Selezionare Modifica
  3. Verificare l'attività di sviluppo delle estensioni di Visual Studio.
  4. Aprire il nodo di sviluppo dell'estensione di Visual Studio nell'albero di riepilogo.
  5. Assicurarsi che la casella .NET Compiler Platform SDK sia selezionata.
  6. Selezionare Modifica.

Facoltativamente, è anche necessario che l'editor DGML visualizzi i grafici nel visualizzatore:

  1. Aprire il nodo Singoli componenti nell'albero di riepilogo.
  2. Selezionare la casella per l'editor DGML

Eseguire l'installazione usando la scheda Programma di installazione di Visual Studio - Singoli componenti

  1. Eseguire il programma di installazione di Visual Studio
  2. Selezionare Modifica
  3. Selezionare la scheda Singoli componenti
  4. Selezionare la casella .NET Compiler Platform SDK. È disponibile nella parte superiore della sezione Compilatori, strumenti di compilazione e runtime .
  5. Selezionare Modifica.

Facoltativamente, è anche necessario che l'editor DGML visualizzi i grafici nel visualizzatore:

  1. Selezionare la casella di controllo per l'editor DGML. È disponibile nella sezione Strumenti di codice .

Informazioni su compilazioni e simboli

Man mano che si lavora di più con .NET Compiler SDK, si acquisisce familiarità con le distinzioni tra l'API sintassi e l'API semantica. L'API Sintassi consente di esaminare la struttura di un programma. Tuttavia, spesso si vogliono informazioni più complete sulla semantica o sul significato di un programma. Anche se un file di codice libero o un frammento di codice Visual Basic o C# può essere analizzato in modo sintattico in isolamento, non è significativo porre domande come "qual è il tipo di questa variabile" in un vuoto. Il significato di un nome di tipo può dipendere da riferimenti ad assembly, importazioni di namespace o altri file di codice. Queste domande vengono risposte usando l'API Semantica, in particolare la Microsoft.CodeAnalysis.Compilation classe .

Un'istanza di Compilation è analoga a un singolo progetto come illustrato dal compilatore e rappresenta tutti gli elementi necessari per compilare un programma Visual Basic o C#. La compilazione include il set di file di origine da compilare, i riferimenti agli assembly e le opzioni del compilatore. È possibile ragionare sul significato del codice usando tutte le altre informazioni in questo contesto. Un Compilation consente di trovare Simboli - entità come tipi, namespace, membri e variabili a cui fanno riferimento nomi e altre espressioni. Il processo di associazione di nomi ed espressioni ai simboli è denominato Binding.

Come Microsoft.CodeAnalysis.SyntaxTree, Compilation è una classe astratta con derivati specifici del linguaggio. Quando si crea un'istanza di Compilation, è necessario richiamare un metodo factory nella Microsoft.CodeAnalysis.CSharp.CSharpCompilation classe (o Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Esecuzione di query sui simboli

In questa esercitazione viene esaminato di nuovo il programma "Hello World". Questa volta, si eseguono query sui simboli nel programma per comprendere quali tipi rappresentano tali simboli. Si eseguono query per i tipi in uno spazio dei nomi e si apprenderà come trovare i metodi disponibili in un tipo.

È possibile visualizzare il codice completato per questo esempio nel repository GitHub.

Annotazioni

I tipi albero della sintassi usano l'ereditarietà per descrivere i diversi elementi della sintassi validi in posizioni diverse del programma. L'uso di queste API spesso significa effettuare il casting di proprietà o membri di raccolta a tipi derivati specifici. Negli esempi seguenti, l'assegnazione e i cast sono istruzioni separate, usando variabili tipate in modo esplicito. È possibile leggere il codice per visualizzare i tipi restituiti dell'API e il tipo di runtime degli oggetti restituiti. In pratica, è più comune usare variabili tipizzate in modo implicito e basarsi sui nomi api per descrivere il tipo di oggetti da esaminare.

Creare un nuovo progetto C# Stand-Alone Code Analysis Tool :

  • In Visual Studio scegliere File>nuovo>progetto per visualizzare la finestra di dialogo Nuovo progetto.
  • In Visual C#>Extensibility scegliere Strumento di analisi del codice indipendente.
  • Assegnare al progetto il nome "SemanticQuickStart" e fare clic su OK.

Si analizzerà il programma "Hello World!" di base illustrato in precedenza. Aggiungere il testo per il programma Hello World come costante nella Program classe:

        const string programText =
@"using System;
using System.Collections.Generic;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Hello, World!"");
        }
    }
}";

Aggiungere quindi il codice seguente per compilare l'albero della sintassi per il testo del codice nella programText costante . Aggiungere la riga seguente al Main metodo :

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Successivamente, costruisci un oggetto CSharpCompilation dall'albero già creato. L'esempio "Hello World" si basa sui String tipi e Console . È necessario fare riferimento all'assembly che dichiara questi due tipi nella compilazione. Aggiungi la seguente riga al metodo Main per compilare il tuo albero sintattico, includendo il riferimento all'assembly appropriato.

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

Il CSharpCompilation.AddReferences metodo aggiunge riferimenti alla compilazione. Il MetadataReference.CreateFromFile metodo carica un assembly come riferimento.

Esecuzione di query sul modello semantico

Dopo aver ottenuto un Compilation, è possibile chiedergli un SemanticModel per qualsiasi SyntaxTree contenuto in tale Compilation. È possibile considerare il modello semantico come origine per tutte le informazioni che normalmente si ottengono da IntelliSense. Un SemanticModel può rispondere a domande come "Quali nomi sono nell'ambito in questa posizione?", "Quali membri sono accessibili da questo metodo?", "Quali variabili vengono usate in questo blocco di testo?" e "A cosa fa riferimento questo nome/espressione?" Aggiungere questa istruzione per creare il modello semantico:

SemanticModel model = compilation.GetSemanticModel(tree);

Collegamento di un nome

L'Compilation crea il SemanticModel dall'SyntaxTree. Dopo aver creato il modello, è possibile eseguire una query per trovare la prima using direttiva e recuperare le informazioni sui simboli per lo spazio dei nomi System. Aggiungere queste due righe al Main metodo per creare il modello semantico e recuperare il simbolo per la prima using direttiva:

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

Il codice precedente illustra come associare il nome nella prima direttiva using per recuperare un Microsoft.CodeAnalysis.SymbolInfo per lo spazio dei nomi System. Il codice precedente illustra anche l'uso del modello di sintassi per trovare la struttura del codice; si usa il modello semantico per comprenderne il significato. Il modello di sintassi trova la stringa System nella using direttiva . Il modello semantico ha tutte le informazioni sui tipi definiti nello System spazio dei nomi.

Dall'oggetto SymbolInfo è possibile ottenere utilizzando Microsoft.CodeAnalysis.ISymbol la SymbolInfo.Symbol proprietà . Questa proprietà restituisce il simbolo a cui fa riferimento questa espressione. Per le espressioni che non fanno riferimento a nulla ,ad esempio valori letterali numerici, questa proprietà è null. Quando l'oggetto SymbolInfo.Symbol non è Null, ISymbol.Kind indica il tipo del simbolo. In questo esempio la ISymbol.Kind proprietà è .SymbolKind.Namespace Aggiungere il codice seguente al metodo Main. Recupera il simbolo dello spazio dei nomi System e quindi visualizza tutti gli spazi dei nomi figlio dichiarati nello spazio dei nomi System.

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

Eseguire il programma e verrà visualizzato l'output seguente:

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

Annotazioni

L'output non include ogni namespace che è un namespace figlio dello System namespace. Visualizza ogni namespace presente in questa compilazione, che fa riferimento esclusivamente all'assembly in cui System.String è dichiarato. Qualsiasi spazio dei nomi dichiarato in altri assembly non è noto a questa compilazione

Associazione di un'espressione

Il codice precedente mostra come trovare un simbolo associandolo a un nome. Esistono altre espressioni in un programma C# che possono essere associate che non sono nomi. Per illustrare questa funzionalità, è possibile accedere all'associazione a un valore letterale stringa semplice.

Il programma "Hello World" contiene una stringa "Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxHello, World!" visualizzata nella console.

È possibile trovare la stringa "Hello, World!" individuando il singolo valore letterale stringa nel programma. Quindi, dopo aver individuato il nodo della sintassi, ottenere le informazioni sul tipo per tale nodo dal modello semantico. Aggiungere il codice seguente al Main metodo :

// 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 Microsoft.CodeAnalysis.TypeInfo struct include una proprietà TypeInfo.Type che consente l'accesso alle informazioni semantiche sul tipo di literal. In questo esempio questo è il string tipo . Aggiungere una dichiarazione che assegna questa proprietà a una variabile locale:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Per completare questa esercitazione, si creerà una query LINQ che crea una sequenza di tutti i metodi pubblici dichiarati nel string tipo che restituiscono un oggetto string. Questa query diventa complessa, quindi si creerà la riga per riga, quindi la ricostruirà come singola query. L'origine per questa query è la sequenza di tutti i membri dichiarati nel string tipo :

var allMembers = stringTypeSymbol?.GetMembers();

Tale sequenza di origine contiene tutti i membri, incluse le proprietà e i campi, quindi filtrarlo usando il ImmutableArray<T>.OfType metodo per trovare gli elementi che sono Microsoft.CodeAnalysis.IMethodSymbol oggetti:

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

Aggiungere quindi un altro filtro per restituire solo i metodi pubblici e restituire :string

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

Selezionare solo la proprietà 'name' e solo i nomi distinti eliminando eventuali sovraccarichi.

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

È anche possibile compilare la query completa usando la sintassi di query LINQ e quindi visualizzare tutti i nomi dei metodi nella 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);
}

Compilare ed eseguire il programma. Verrà visualizzato l'output seguente:

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

È stata usata l'API Semantica per trovare e visualizzare informazioni sui simboli che fanno parte di questo programma.