Giugno 2016

Volume 31 Numero 6

Il presente articolo è stato tradotto automaticamente.

Piattaforma del compilatore .NET - Generazione di codice indipendente dal linguaggio con Roslyn

Da Alessandro Del Del

Il Roslyn codice base providespowerful API è possibile utilizzare per eseguire l'analisi del codice avanzato tramite il codice sorgente. Ad esempio, analizzatori e il refactoring di codice può scorrere una parte del codice sorgente e sostituire uno o più nodi di sintassi con il nuovo codice che si genera con APIs Roslyn. Un modo comune per eseguire la generazione di codice è tramite la classe SyntaxFactory, che espone i metodi factory per generare i nodi di sintassi in modo che i compilatori possono comprendere. La classe SyntaxFactory è certamente estremamente utile perché consente la generazione di qualsiasi elemento di sintassi possibili, ma esistono due diverse implementazioni di SyntaxFactory: Microsoft.CodeAnalysis.CSharp.SyntaxFactory e Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory. Questa è un'importante implicazione se si desidera scrivere un analizzatore con una correzione del codice che fa riferimento in c# e Visual Basic, è necessario scrivere due analizzatori diversi, uno per c# e uno per Visual Basic, utilizzando le due implementazioni di SyntaxFactory, ognuno con un approccio diverso per le diverse modalità tali linguaggi gestiscono alcuni costrutti. Ciò significa probabilmente sprecare tempo alla scrittura di due volte l'analizzatore e sulla relativa gestione diventa più difficile. Fortunatamente, APIs Roslyn fornisce inoltre il Microsoft.CodeAnalysis.Editing.SyntaxGenerator, che consente la generazione di codice indipendente dal linguaggio. In altre parole, con SyntaxGenerator è possibile scrivere la logica di generazione di codice una volta e di destinazione sia in c# e Visual Basic. In questo articolo vi mostrerò come eseguire la generazione di codice indipendente dal linguaggio con SyntaxGenerator e fornirò alcuni suggerimenti sulle aree di lavoro API Roslyn.

A partire da codice

Iniziamo con codice sorgente che verrà generato utilizzando SyntaxGenerator. Si consideri la semplice classe Person che implementa l'interfaccia ICloneable in c# (Figura 1) e Visual Basic (Figura 2).

Figura 1 classe Person semplice in c#

public abstract class Person : ICloneable
{
  // Not using auto-props is intentional for demo purposes
  private string _lastName;
  public string LastName
  {
    get
    {
      return _lastName;
    }
    set
    {
      _lastName = value;
    }
  }
  private string _firstName;
  public string FirstName
  {
    get
    {
      return _firstName;
    }
    set
    {
      _firstName = value;
    }
  }
  public Person(string LastName, string FirstName)
  {
    _lastName = LastName;
    _firstName = FirstName;
  }
  public virtual object Clone()
  {
    return MemberwiseClone();
  }
}

Figura 2 classe Person semplice in Visual Basic

Public MustInherit Class Person
  Implements ICloneable
  'Not using auto-props is intentional for demo purposes
  Private _lastName As String
  Private _firstName As String
  Public Property LastName As String
    Get
      Return _lastName
    End Get
    Set(value As String)
      _lastName = value
    End Set
  End Property
  Public Property FirstName As String
    Get
      Return _firstName
    End Get
    Set(value As String)
      _firstName = value
    End Set
  End Property
  Public Sub New(LastName As String, FirstName As String)
    _lastName = LastName
    _firstName = FirstName
  End Sub
  Public Overridable Function Clone() As Object Implements ICloneable.Clone
    Return MemberwiseClone()
  End Function
End Class

È probabile che si può obiettare che la dichiarazione di proprietà implementate automaticamente avrebbe lo stesso effetto e mantengono codice più pulito in questo caso specifico, ma in seguito si vedrà perché si utilizzi la forma espansa.

Questa implementazione della classe Person è molto semplice, ma contiene una serie di elementi di sintassi, rendendo utile per informazioni su come eseguire la generazione di codice con SyntaxGenerator. Generare la classe roslyn.

Creazione di uno strumento di analisi codice

La prima cosa da fare è creare un nuovo progetto in Visual Studio 2015 con riferimenti alle librerie di Roslyn. A causa dello scopo generale di questo articolo, anziché la creazione di un analizzatore o effettuare il refactoring, si sceglierà un altro modello di progetto disponibile nel SDK di piattaforma del compilatore .NET, l'autonomo strumento di analisi codice, disponibili nel nodo dell'estendibilità della finestra di dialogo Nuovo progetto (vedere Figura 3).

Il modello di progetto di codice autonomi Analysis Tool
Figura 3. il modello di progetto di codice autonomi Analysis Tool

Questo modello di progetto genera effettivamente un'applicazione console e aggiunge automaticamente i pacchetti NuGet appropriati a APIs Roslyn, per la lingua di propria scelta. Poiché lo scopo è quello di destinazione sia in c# e Visual Basic, la prima cosa da fare è aggiungere i pacchetti NuGet per la seconda lingua. Ad esempio, se è stato creato inizialmente un progetto c#, è necessario scaricare e installare le librerie di Visual Basic seguente da NuGet:

  • Microsoft.CodeAnalysis.VisualBasic.dll
  • Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll
  • Microsoft.CodeAnalysis.VisualBasic.Workspaces.Common.dll

È sufficiente installare solo quest'ultimo da NuGet e questo si risolverà automaticamente le dipendenze per le altre librerie richieste. Risoluzione delle dipendenze è importante ogni volta che si prevede di utilizzare la classe SyntaxGenerator, indipendentemente da quale modello di progetto in uso. In questo caso verranno restituite eccezioni di in fase di esecuzione.

Soddisfare SyntaxGenerator e le API di aree di lavoro

La classe SyntaxGenerator espone un metodo statico denominato GetGenerator, che restituisce un'istanza di SyntaxGenerator. Utilizzare l'istanza restituita per eseguire la generazione di codice. GetGenerator presenta tre overload seguenti:

public static SyntaxGenerator GetGenerator(Document document)
public static SyntaxGenerator GetGenerator(Project project)
public static SyntaxGenerator GetGenerator(Workspace workspace, string language)

I primi due overload di funzionare con un documento e un progetto, rispettivamente. La classe documento rappresenta un file di codice in un progetto, mentre la classe di progetto rappresenta un progetto di Visual Studio nel suo complesso. Questi overload rilevano automatico della lingua (in c# o Visual Basic) il documento o un progetto di destinazione. Documenti, progetti e soluzioni (una classe aggiuntiva che rappresenta una soluzione di Visual Studio. sln) fanno parte di un'area di lavoro, che fornisce un modo gestito di interagire con tutti gli elementi che costituiscono una soluzione di MSBuild con progetti, file di codice, metadati e oggetti. Le API di aree di lavoro espone diverse classi, che è possibile utilizzare per gestire le aree di lavoro, ad esempio la classe MSBuildWorkspace, che consente l'utilizzo di una soluzione. sln o la classe AdhocWorkspace, che invece è molto utile quando si lavora non in una soluzione di MSBuild esistente ma un'area di lavoro in memoria che rappresenta uno. Nel caso di analisi e il refactoring di codice, si dispone già di un'area di lavoro di MSBuild che consente di lavorare con i file di codice utilizzando istanze delle classi di documenti, progetti e soluzioni. Nel progetto di esempio corrente, nessuna area di lavoro, pertanto creiamo una usando il terzo overload SyntaxGenerator. Per ottenere una nuova area di lavoro vuoto, è possibile utilizzare la classe AdhocWorkspace:

// Get a workspace
var workspace = new AdhocWorkspace();

A questo punto è possibile ottenere un'istanza di SyntaxGenerator, passando l'istanza dell'area di lavoro e la lingua desiderata come argomenti:

// Get the SyntaxGenerator for the specified language
var generator = SyntaxGenerator.GetGenerator(workspace, LanguageNames.CSharp);

Il nome della lingua può essere CSharp o VisualBasic, entrambe le costanti dalla classe LanguageNames. Iniziamo con c#. più avanti verrà illustrato come modificare il nome della lingua in Visual Basic. Si dispongono di tutti gli strumenti è ora necessario ed è possibile generare nodi di sintassi.

Generazione di nodi di sintassi

La classe SyntaxGenerator espone i metodi factory istanza che generano la sintassi corretta nodi in modo che sia compatibile con la grammatica e la semantica di c# e Visual Basic. Ad esempio, i metodi con nomi che terminano con il suffisso espressione generano espressioni; metodi con nomi che terminano con il suffisso istruzione generano istruzioni; metodi con nomi che terminano con il suffisso dichiarazione generano le dichiarazioni. Per ogni categoria, sono disponibili metodi specializzati che generano i nodi sintassi specifica. Ad esempio, MethodDeclaration genera un blocco di metodi, PropertyDeclaration genera una proprietà, FieldDeclaration genera un campo e così via (e, come di consueto, IntelliSense è il migliore amico). Peculiarità di questi metodi è che ogni restituisce SyntaxNode, anziché un tipo specializzato che deriva da SyntaxNode, come accade con la classe SyntaxFactory. Ciò offre una notevole flessibilità, soprattutto durante la generazione di nodi complessi.

In base alla classe Person di esempio, la prima cosa da generare è una direttiva di utilizzo/importazione per lo spazio dei nomi System, che espone l'interfaccia ICloneable. Questo può essere eseguita con il metodo NamespaceImportDeclaration come indicato di seguito:

// Create using/Imports directives
var usingDirectives = generator.NamespaceImportDeclaration("System");

Questo metodo accetta un argomento stringa che rappresenta lo spazio dei nomi da importare. Procediamo e dichiarare due campi, che viene eseguita tramite il metodo FieldDeclaration:

// Generate two private fields
var lastNameField = generator.FieldDeclaration("_lastName",
  generator.TypeExpression(SpecialType.System_String),
  Accessibility.Private);
var firstNameField = generator.FieldDeclaration("_firstName",
  generator.TypeExpression(SpecialType.System_String),
  Accessibility.Private);

FieldDeclaration accetta il nome del campo, il tipo di campo e il livello di accessibilità come argomenti. Per specificare il tipo appropriato, si richiama il metodo TypeExpression, che accetta un valore dall'enumerazione SpecialType, in questo caso System_String (non dimenticare di utilizzare IntelliSense per individuare altri valori). Il livello di accesso facilitato è impostato con un valore dall'enumerazione accessibilità. Quando si richiamano metodi da SyntaxGenerator, è molto comune per nidificare le chiamate ad altri metodi dalla stessa classe, come nel caso di TypeExpression. Il passaggio successivo è la generazione di due proprietà, quale operazione viene eseguita richiamando il metodo PropertyDeclaration, illustrato nella Figura 4.

Figura 4, la generazione di due proprietà tramite il metodo PropertyDeclaration

// Generate two properties with explicit get/set
var lastNameProperty = generator.PropertyDeclaration("LastName",
  generator.TypeExpression(SpecialType.System_String), Accessibility.Public,
  getAccessorStatements:new SyntaxNode[]
  { generator.ReturnStatement(generator.IdentifierName("_lastName")) },
  setAccessorStatements:new SyntaxNode[]
  { generator.AssignmentStatement(generator.IdentifierName("_lastName"),
  generator.IdentifierName("value"))});
var firstNameProperty = generator.PropertyDeclaration("FirstName",
  generator.TypeExpression(SpecialType.System_String),
  Accessibility.Public,
  getAccessorStatements: new SyntaxNode[]
  { generator.ReturnStatement(generator.IdentifierName("_firstName")) },
  setAccessorStatements: new SyntaxNode[]
  { generator.AssignmentStatement(generator.IdentifierName("_firstName"),
  generator.IdentifierName("value")) });

Come può notare, la generazione di un nodo di sintassi per una proprietà è più complessa. In questo caso è comunque passare una stringa con il nome della proprietà, quindi un TypeExpression per il tipo di proprietà, quindi il livello di accessibilità. Con una proprietà è necessario inoltre in genere fornire le funzioni di accesso Get e Set, soprattutto per i casi in cui è necessario eseguire codice diverso per l'impostazione o restituzione del valore di proprietà (ad esempio, genera l'evento OnPropertyChanged quando si implementa l'interfaccia INotifyPropertyChanged). Accesso Get e Set è rappresentate da una matrice di oggetti SyntaxNode. In Get, in genere restituiscono il valore della proprietà, quindi, qui il codice richiama il metodo ReturnStatement, che rappresenta l'istruzione return più il valore o un oggetto restituisce. In questo caso, il valore restituito è l'identificatore del campo. Un nodo di sintassi di un identificatore viene ottenuto chiamando il metodo IdentifierName, che accetta un argomento di tipo stringa e restituisce comunque SyntaxNode. Funzioni di accesso Set, invece, archiviare il valore della proprietà in un campo tramite un'assegnazione. Le assegnazioni sono rappresentate dal metodo AssignmentStatement, che accetta due argomenti, i lati sinistro e destro dell'assegnazione. Nella fattispecie, l'assegnazione è tra due identificatori, pertanto il codice richiama IdentifierName due volte, uno per il lato sinistro dell'assegnazione (il nome del campo) e uno per il lato destro (il valore della proprietà). Poiché il valore della proprietà è rappresentato dall'identificatore di valore in c# e Visual Basic, può essere hardcoded.

Il passaggio successivo è la generazione di codice per il metodo Clone, che è necessario per l'implementazione dell'interfaccia ICloneable. In genere, un metodo è costituito da della dichiarazione, che include i delimitatori di firma e di blocco, e di un numero di istruzioni, che costituiscono il corpo del metodo. Nell'esempio corrente Clone deve inoltre implementare il metodo ICloneable. clone. Per questo motivo, un approccio pratico è dividere la generazione di codice per il metodo in tre nodi sintassi più piccoli. Il primo nodo di sintassi è il corpo del metodo, che presenta un aspetto simile al seguente:

// Generate the method body for the Clone method
var cloneMethodBody = generator.ReturnStatement(generator.
  InvocationExpression(generator.IdentifierName("MemberwiseClone")));

In questo caso, il metodo Clone restituisce il risultato della chiamata al metodo MemberwiseClone eredita da System. Object. Per questo motivo, il corpo del metodo è solo una chiamata a ReturnStatement, che siano soddisfatti in precedenza. In questo caso, l'argomento del ReturnStatement è una chiamata del metodo InvocationExpression, che rappresenta una chiamata al metodo e il cui parametro è un identificatore che rappresenta il nome del metodo richiamato. Poiché l'argomento InvocationExpression è di tipo SyntaxNode, un modo pratico per fornire l'identificatore viene utilizzato il metodo IdentifierName, passando la stringa che rappresenta l'identificatore del metodo da richiamare. Se si dispone di un metodo con un corpo del metodo più complesso, sarà necessario generare una matrice di tipo SyntaxNode, con ogni nodo che rappresenta un codice nel corpo del metodo.

Il passaggio successivo è la generazione di dichiarazione del metodo Clone, che viene eseguita come segue:

// Generate the Clone method declaration
var cloneMethoDeclaration = generator.MethodDeclaration("Clone", null,
  null,null,
  Accessibility.Public,
  DeclarationModifiers.Virtual,
  new SyntaxNode[] { cloneMethodBody } );

Si genera un metodo con il metodo MethodDeclaration. Verrà visualizzato un numero di argomenti, ad esempio:

  • il nome del metodo, di tipo String
  • i parametri del metodo, di tipo IEnumerable < SyntaxNode > (null in questo caso)
  • i parametri di tipo per i metodi generici, di tipo IEnumerable < SyntaxNode > (null in questo caso)
  • il tipo restituito, di tipo SyntaxNode (null in questo caso)
  • il livello di accessibilità, con un valore dall'enumerazione accessibilità
  • i modificatori di dichiarazione, con uno o più valori dell'enumerazione DeclarationModifiers; In questo caso il modificatore è virtuale (Overridable in Visual Basic)
  • le istruzioni per il corpo del metodo di tipo SyntaxNode; In questo caso, la matrice contiene un elemento, ovvero l'istruzione return definito in precedenza

Verrà visualizzato un esempio di come aggiungere i parametri del metodo con il metodo ConstructorDeclaration più specializzato a breve. Il metodo Clone deve implementare la controparte dall'interfaccia ICloneable, pertanto questa operazione deve essere gestita. È necessario ora è un nodo di sintassi che rappresenta il nome dell'interfaccia e che saranno utili anche quando l'implementazione dell'interfaccia viene aggiunto alla classe Person. Questa operazione può essere eseguita richiamando il metodo IdentifierName, che restituisce un nome corretto della stringa specificata:

// Generate a SyntaxNode for the interface's name you want to implement
var ICloneableInterfaceType = generator.IdentifierName("ICloneable");

Se si desidera importare il nome completo, System. ICloneable, si utilizzerebbe DottedName anziché IdentifierName per generare un nome completo corretto, ma nell'esempio corrente è già stata aggiunta una NamespaceImportDeclaration di sistema. A questo punto, è possibile riunire tutto. SyntaxGenerator dispone dei metodi AsPublicInterfaceImplementation e AsPrivateInterfaceImplementation che consente di indicare al compilatore che una definizione di metodo implementa un'interfaccia, come illustrato di seguito:

// Explicit ICloneable.Clone implemenation
var cloneMethodWithInterfaceType = generator.
  AsPublicInterfaceImplementation(cloneMethoDeclaration,
  ICloneableInterfaceType);

Ciò è particolarmente importante con Visual Basic, che richiede in modo esplicito la clausola Implements. AsPublicInterfaceImplementation è l'equivalente dell'implementazione dell'interfaccia implicita in c#, mentre AsPrivateInterfaceImplementation è l'equivalente dell'implementazione esplicita dell'interfaccia. Entrambi funzionano con metodi, proprietà e indicizzatori.

Il passaggio successivo riguarda il costruttore, che viene eseguito tramite il metodo ConstructorDeclaration di generazione. Come per il metodo Clone, definizione del costruttore deve essere suddivisa in parti più piccole per il codice più pulito e più facile comprensione. Come ricorderete Figura 1 e Figura 2, il costruttore accetta due parametri di tipo stringa, quali sono necessari per l'inizializzazione della proprietà. Pertanto è consigliabile generare innanzitutto il nodo di sintassi per entrambi i parametri:

// Generate parameters for the class' constructor
var constructorParameters = new SyntaxNode[] {
  generator.ParameterDeclaration("LastName",
  generator.TypeExpression(SpecialType.System_String)),
  generator.ParameterDeclaration("FirstName",
  generator.TypeExpression(SpecialType.System_String)) };

Ogni parametro viene generato con il metodo ParameterDeclaration, che accetta una stringa che rappresenta il nome del parametro e un'espressione che rappresenta il tipo di parametro. Entrambi i parametri sono di tipo stringa, pertanto il codice utilizza semplicemente il metodo TypeExpression, come illustrato in precedenza. Il motivo per imballaggio entrambi i parametri in un SyntaxNode è che il ConstructorDeclaration richiede un oggetto di questo tipo per rappresentare i parametri.

A questo punto è necessario costruire il corpo del metodo, che sfrutta i vantaggi del metodo AssignmentStatement che si è visto in precedenza, come segue:

// Generate the constructor's method body
var constructorBody = new SyntaxNode[] {
  generator.AssignmentStatement(generator.IdentifierName("_lastName"),
  generator.IdentifierName("LastName")),
  generator.AssignmentStatement(generator.IdentifierName("_firstName"),
  generator.IdentifierName("FirstName"))};

In questo caso esistono due istruzioni, entrambi raggruppate in un oggetto SyntaxNode. Infine, è possibile generare il costruttore, combinazione di parametri e il corpo del metodo:

// Generate the class' constructor
var constructor = generator.ConstructorDeclaration("Person",
  constructorParameters, Accessibility.Public,
  statements:constructorBody);

ConstructorDeclaration è simile a MethodDeclaration, ma è progettato specificamente per generare un metodo. ctor in c# e un metodo Sub New in Visual Basic.

Generazione di un CompilationUnit

Finora si è appreso come generare codice per ogni membro della classe di persona. A questo punto è necessario riunire questi membri e generare un SyntaxNode corretto per la classe. Membri della classe devono essere forniti sotto forma di un SyntaxNode e di seguito viene illustrato come mettere insieme tutti i membri creati in precedenza:

// An array of SyntaxNode as the class members
var members = new SyntaxNode[] { lastNameField,
  firstNameField, lastNameProperty, firstNameProperty,
  cloneMethodWithInterfaceType, constructor };

Ora infine è possibile generare la classe Person, sfruttando i vantaggi del metodo ClassDeclaration come indicato di seguito:

// Generate the class
var classDefinition = generator.ClassDeclaration(
  "Person", typeParameters: null,
  accessibility: Accessibility.Public,
  modifiers: DeclarationModifiers.Abstract,
  baseType: null,
  interfaceTypes: new SyntaxNode[] { ICloneableInterfaceType },
  members: members);

Come con altri tipi di dichiarazioni, questo metodo richiede la specifica il nome, il tipo generico (null in questo caso), il livello di accessibilità, i modificatori (astratto in questo caso), o MustInherit in Visual Basic, di base (null in questo caso) i tipi e le interfacce implementate (in questo caso un SyntaxNode contenente il nome dell'interfaccia creata in precedenza come nodo sintassi). È anche possibile incapsulare la classe in uno spazio dei nomi. SyntaxGenerator include il metodo NamespaceDeclaration, che accetta il nome dello spazio dei nomi e il SyntaxNode contiene. Utilizzare simile al seguente:

// Declare a namespace
var namespaceDeclaration = generator.NamespaceDeclaration("MyTypes", classDefinition);

I compilatori sappia come gestire il nodo di sintassi generati per spazio dei nomi completo e membri nidificati e come eseguire l'analisi del codice tramite la sintassi, ma a volte è necessario restituire il risultato sotto forma di un CompilationUnit, un tipo che rappresenta un file di codice. Si tratta di una tipica con gli analizzatori e il refactoring di codice. Ecco il codice che scritto per restituire un CompilationUnit:

// Get a CompilationUnit (code file) for the generated code
var newNode = generator.CompilationUnit(usingDirectives, namespaceDeclaration).
  NormalizeWhitespace();

Questo metodo accetta una o più istanze di SyntaxNode come argomento.

L'Output in c# e Visual Basic

Dopo tutto questo lavoro, si è pronti per visualizzare il risultato. Figura 5 Mostra il codice c# generato per la classe Person.

Il linguaggio c# Roslyn codice generato per la classe Person
Figura 5 il codice c# generato Roslyn per la classe Person

A questo punto, è sufficiente impostare la lingua VisualBasic nella riga di codice che crea un nuovo AdhocWorkspace:

generator = SyntaxGenerator.GetGenerator(workspace, LanguageNames.VisualBasic);

Se si esegue nuovamente il codice, si otterrà una definizione di classe Visual Basic, come illustrato nella Figura 6.

Il codice Visual Basic generato Roslyn per la classe Person
Figura 6 il codice di Visual Basic generato Roslyn per la classe Person

Il punto chiave è che, con SyntaxGenerator, è stato una volta scritto il codice e sono stati in grado di generare codice c# e Visual Basic che consente di utilizzare l'API di analisi di Roslyn. Una volta terminato, non dimenticare di richiamare il metodo Dispose sull'istanza AdhocWorkspace, o semplicemente racchiudere il codice all'interno di un utilizzo istruzione. Poiché il codice generato potrebbe contenere errori, è inoltre possibile controllare la proprietà ContainsDiagnostics per verificare se eventuali diagnostica presenti nel codice e ottiene informazioni dettagliate sui problemi di codice tramite il metodo GetDiagnostics nessuno è perfetto.

Refactoring e gli analizzatori indipendente dalla lingua

È possibile utilizzare la classe SyntaxGenerator e APIs Roslyn ogni volta che si desidera eseguire un'analisi approfondita al codice sorgente, ma questo approccio è molto utile con gli analizzatori e il refactoring di codice. In effetti, analizzatori e correzioni del codice refactoring hanno attributi DiagnosticAnalyzer, ExportCodeFixProvider ed ExportCodeRefactoringProvider, rispettivamente, ciascuna delle quali accetta le lingue supportate primarie e secondarie. Utilizzando SyntaxGenerator anziché SyntaxFactory, è possibile assegnare contemporaneamente in c# e Visual Basic.

Avvolgendo

La classe SyntaxGenerator dallo spazio dei nomi Microsoft.CodeAnalysis.Editing fornisce un modo indipendente dal linguaggio per la generazione di nodi di sintassi, la destinazione di c# e Visual Basic con un codice di base. Questa classe potente è possibile generare qualsiasi elemento di sintassi possibili in modo che sia compatibile con entrambi i compilatori, risparmiando tempo e migliorare la gestibilità del codice.


Alessandro Del Soleè stato un MVP Microsoft dal 2008. Assegnati MVP di cinque volte l'anno, egli ha scritto molti libri, eBook, video didattici e articoli sullo sviluppo .NET con Visual Studio. Del Sole funziona come un esperto di sviluppatore di soluzioni per cervello Sys (cervello sys.it), messa a fuoco sullo sviluppo .NET, consulenza e formazione. È possibile seguirlo su Twitter: @progalex.

Grazie ai seguenti esperti tecnici Microsoft per la revisione di questo articolo: Anthony D. Green e Matt Warren
Anthony D. Green è Program Manager per Visual Basic. Anthony ha lavorato anche in che cosa Roslyn per 5 anni. È di Chicago e che è possibile trovarlo su Twitter @ThatVBGuy