Partager via



Juin 2016

Volume 31, numéro 6

Cet article a fait l'objet d'une traduction automatique.

Plateforme du compilateur .NET - Génération de code sans langage prédéfini avec Roslyn

Par Alessandro Del Del

Le Roslyn code base fournit depuissants API que vous pouvez exploiter pour effectuer une analyse de code riches sur votre code source. Par exemple, analyseurs de code refactorisations peuvent guider un morceau de code source et remplacer un ou plusieurs nœuds de syntaxe avec le nouveau code que vous générez avec APIs Roslyn. Une façon courante d’effectuer la génération de code est via la classe SyntaxFactory, qui expose les méthodes de fabrique pour générer des nœuds de la syntaxe d’une façon qui peuvent comprendre les compilateurs. La classe SyntaxFactory est certainement très puissante, car elle permet de générer tout élément de syntaxe possible, mais il existe deux implémentations SyntaxFactory différentes : Microsoft.CodeAnalysis.CSharp.SyntaxFactory et Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory. Ceci a une implication importante si vous souhaitez écrire un analyseur avec une correction du code qui cible les langages c# et Visual Basic, vous devez écrire deux différents analyseurs, un pour c# et un pour Visual Basic, les deux implémentations de SyntaxFactory, chacun avec une approche différente en raison de l’autre façon ces langages gérer certaines constructions. Cela signifie probablement que de perdre du temps à écrire deux fois de l’Analyseur de, et leur maintenance devient plus difficile. Heureusement, APIs Roslyn fournissent également Microsoft.CodeAnalysis.Editing.SyntaxGenerator, ce qui permet une génération de code de langage. En d’autres termes, avec SyntaxGenerator, vous pouvez écrire votre logique de génération de code une seule fois et cibler les langages c# et Visual Basic. Dans cet article je vais montrer vous effectuer la génération de code de langage avec SyntaxGenerator, et je vais vous donner quelques conseils sur les API d’espaces de travail Roslyn.

Commençant par Code

Commençons par du code source qui sera généré à l’aide de SyntaxGenerator. Considérons la classe Person simple qui implémente l’interface ICloneable dans c# (Figure 1) et Visual Basic (Figure 2).

Figure 1 une personne Simple classe en 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();
  }
}

Figure 2 une personne Simple classe en 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

Vous serez probablement dire que la déclaration des propriétés implémentées automatiquement auront le même effet et conservez code beaucoup plus clair dans ce cas particulier, mais plus tard, vous verrez pourquoi j’utilise la forme étendue.

Cette implémentation de la classe Person est très simple, mais il contient un grand nombre d’éléments de syntaxe, ce qui vous permet de comprendre comment effectuer la génération de code avec SyntaxGenerator. Nous allons générer la classe avec Roslyn.

Création d’un outil d’analyse de Code

La première chose à faire est de créer un nouveau projet dans Visual Studio 2015 avec des références aux bibliothèques Roslyn. En raison de l’utilisation générale de cet article, au lieu de la création d’un analyseur ou la refactorisation, je vais choisir un autre modèle de projet disponible dans le SDK de plateforme du compilateur .NET, l’outil d’analyse Code autonome, disponible dans le nœud de l’extensibilité de la boîte de dialogue Nouveau projet (voir Figure 3).

Le modèle de projet de Code autonome analyse outil
Figure 3 le modèle de projet de Code autonome analyse outil

Ce modèle de projet génère une application console et ajoute automatiquement les packages NuGet appropriés pour les APIs Roslyn, ciblant la langue de votre choix. Étant donné que l’idée est de cibler les langages c# et Visual Basic, la première chose à faire est ajouter les packages NuGet pour la seconde langue. Par exemple, si vous avez créé au départ d’un projet c#, vous devez télécharger et installer les bibliothèques Visual Basic suivants à partir de NuGet :

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

Vous pouvez simplement installer ce dernier à partir de NuGet, et cela résout automatiquement les dépendances pour les autres bibliothèques requises. Il est important de résolution des dépendances à chaque fois que vous prévoyez d’utiliser la classe SyntaxGenerator, quel que soit le modèle de projet vous utilisez. Cela surviendra provoquent des exceptions au moment de l’exécution.

Répondre à SyntaxGenerator et les API d’espaces de travail

La classe SyntaxGenerator expose une méthode statique nommée GetGenerator, qui retourne une instance de SyntaxGenerator. Vous utilisez l’instance retournée pour effectuer la génération de code. GetGenerator a trois surcharges suivantes :

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

Les deux premières surcharges fonctionnent par rapport à un Document et un projet, respectivement. La classe de Document représente un fichier de code dans un projet, tandis que la classe de projet représente un projet Visual Studio dans son ensemble. Ces surcharges détectent automatiquement la langue (c# ou Visual Basic) à la cible du Document ou un projet. Documents, des projets et des solutions (une classe supplémentaire qui représente une solution .sln de Visual Studio) font partie d’un espace de travail, qui fournit un moyen managé d’interagir avec tout ce qui constitue une solution de MSBuild avec les projets, les fichiers de code, les métadonnées et les objets. Les API d’espaces de travail exposer plusieurs classes, que vous pouvez utiliser pour gérer les espaces de travail, telles que la classe MSBuildWorkspace, qui permet de travailler sur une solution .sln ou la classe AdhocWorkspace, qui est très utile lorsque vous ne travaillez pas avec une solution de MSBuild mais un espace de travail en mémoire qui représente une. Dans le cas des analyseurs et code refactorisations, vous disposez déjà d’un espace de travail MSBuild qui permet de travailler sur les fichiers de code à l’aide des instances des classes de Document, projet et Solution. Dans l’exemple de projet en cours, il n’existe aucun espace de travail, nous allons créer un à l’aide de la troisième surcharge de SyntaxGenerator. Pour obtenir un espace de travail vide, vous pouvez utiliser la classe AdhocWorkspace :

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

Vous pouvez désormais obtenir une instance de SyntaxGenerator, en passant l’instance de l’espace de travail et de la langue souhaitée en tant qu’arguments :

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

Le nom de la langue peut être CSharp ou VisualBasic, les deux constantes de la classe LanguageNames. Commençons par c# ; plus tard, vous verrez comment modifier le nom du langage en Visual Basic. Vous avez tous les outils dont vous avez besoin maintenant et que vous êtes prêt à générer des nœuds de syntaxe.

Génération de nœuds de la syntaxe

La classe SyntaxGenerator expose les méthodes de fabrique d’instance qui génèrent des nœuds de la syntaxe correcte d’une manière qui soit conforme à la syntaxe et la sémantique de c# et Visual Basic. Par exemple, les méthodes dont les noms se terminant par le suffixe Expression génèrent des expressions ; génèrent des méthodes avec des noms se terminant par le suffixe d’instruction instructions ; les méthodes dont les noms se terminant par le suffixe de déclaration génèrent des déclarations. Pour chaque catégorie, il existe des méthodes spécialisées qui génèrent des nœuds de syntaxe spécifique. Par exemple, MethodDeclaration génère un bloc de méthode, PropertyDeclaration génère une propriété, l’élément FieldDeclaration génère un champ et ainsi de suite (et, comme d’habitude, IntelliSense est votre meilleur ami). La particularité de ces méthodes est que chacune retourne SyntaxNode, au lieu d’un type spécialisé qui dérive de SyntaxNode, que se passe-t-il avec la classe SyntaxFactory. Cela fournit une grande souplesse, en particulier lors de la génération de nœuds complexes.

En fonction de l’exemple de classe Person, la première chose à générer est une directive using/Imports pour l’espace de noms System, qui expose l’interface ICloneable. Cela peut être accompli avec la méthode NamespaceImportDeclaration comme suit :

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

Cette méthode prend un argument de chaîne qui représente l’espace de noms à importer. Procédons à déclarer deux champs, qui s’effectue via la méthode de l’élément 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);

L’élément FieldDeclaration prend le nom du champ, le type de champ et le niveau d’accessibilité comme arguments. Pour fournir le type approprié, vous appelez la méthode TypeExpression, qui prend une valeur de l’énumération SpecialType, dans ce cas System_String (n’oubliez pas d’utiliser IntelliSense pour découvrir d’autres valeurs). Le niveau d’accessibilité est défini avec la valeur de l’énumération d’accessibilité. Lorsque vous appelez les méthodes de SyntaxGenerator, il est très courant d’imbriquer les appels à d’autres méthodes de la même classe, comme dans le cas de TypeExpression. L’étape suivante consiste à générer deux propriétés, qui s’effectue en appelant la méthode PropertyDeclaration dans Figure 4.

Figure 4 génération deux propriétés via la méthode 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")) });

Comme vous pouvez le voir, la génération d’un nœud de syntaxe pour une propriété est plus complexe. Ici, vous passez toujours une chaîne avec le nom de propriété, puis un TypeExpression pour le type de propriété, puis le niveau d’accessibilité. Avec une propriété, vous devez généralement fournir les accesseurs Get et Set, en particulier pour les situations dans lesquelles vous devez exécuter du code pour définir ou retourner la valeur de propriété (par exemple, pour déclencher l’événement OnPropertyChanged lorsque vous implémentez l’interface INotifyPropertyChanged) autre que. Les accesseurs Get et Set sont représentées par un tableau d’objets de SyntaxNode. Dans l’opération Get, vous renvoient la valeur de propriété, ici le code appelle la méthode ReturnStatement, qui représente l’instruction de retour, ainsi que la valeur ou un objet qu’il retourne. Dans ce cas, la valeur retournée est identificateur d’un champ. Un nœud de la syntaxe d’un identificateur est obtenu en appelant la méthode NomIdentificateur, qui prend un argument de type chaîne et renvoie toujours SyntaxNode. En revanche, les accesseurs Set stockent la valeur de propriété dans un champ via une attribution. Les affectations sont représentées par la méthode AssignmentStatement, qui prend deux arguments, les côtés gauche et droit de l’assignation. Dans le cas en cours, l’attribution est entre deux identificateurs, donc le code appelle NomIdentificateur à deux reprises, une pour le côté gauche de l’assignation (le nom du champ) et une pour le côté droit (la valeur de propriété). Étant donné que la valeur de propriété est représentée par l’identificateur de la valeur en c# et Visual Basic, il peut être codé en dur.

L’étape suivante est la génération de code pour la méthode Clone, qui est requis par l’implémentation d’interface ICloneable. En règle générale, une méthode se compose de la déclaration, qui comprend les séparateurs de signature et de bloc, et d’un nombre d’instructions qui composent le corps de méthode. Dans l’exemple actuel, Clone doit également implémenter la méthode ICloneable.Clone. Pour cette raison, une approche pratique consiste à fractionner la génération de code pour la méthode en trois nœuds de syntaxe plus petits. Le premier nœud de la syntaxe est le corps de la méthode, qui ressemble à ceci :

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

Dans ce cas, la méthode Clone retourne le résultat de l’appel à la méthode MemberwiseClone que hérite de System.Object. Pour cette raison, le corps de méthode est simplement un appel à ReturnStatement, ce qui vous rempli précédemment. Dans ce cas, l’argument de le ReturnStatement est un appel à la méthode InvocationExpression, qui représente un appel de méthode et dont paramètre est un identificateur représentant le nom de la méthode appelée. L’argument InvocationExpression est de type SyntaxNode, un moyen pratique de fournir l’identificateur est à l’aide de la méthode NomIdentificateur, en passant la chaîne qui représente l’identificateur de la méthode à appeler. Si vous aviez une méthode avec un corps de méthode plus complexe, vous devez générer un tableau du type SyntaxNode, chaque nœud représentant un code dans le corps de la méthode.

L’étape suivante consiste à générer la déclaration de méthode Clone, qui s’effectue comme suit :

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

Vous générez une méthode avec la méthode MethodDeclaration. Cela prend un nombre d’arguments, tels que :

  • le nom de la méthode, de type chaîne
  • les paramètres de méthode de type IEnumerable < SyntaxNode > (null dans le cas présent)
  • les paramètres de type pour les méthodes génériques, de type IEnumerable < SyntaxNode > (null dans le cas présent)
  • le type de retour de type SyntaxNode (null dans le cas présent)
  • le niveau d’accessibilité, avec une valeur de l’énumération d’accessibilité
  • les modificateurs de déclaration, avec une ou plusieurs valeurs de l’énumération DeclarationModifiers ; Dans ce cas le modificateur est virtuelle (Overridable en Visual Basic)
  • les instructions pour le corps de la méthode de type SyntaxNode ; Dans ce cas, le tableau contient un élément, qui est l’instruction return définie précédemment

Vous verrez un exemple montrant comment ajouter des paramètres de méthode avec la méthode ConstructorDeclaration plus spécialisée peu de temps. La méthode Clone doit implémenter son équivalent à partir de l’interface ICloneable, donc cela doit être gérée. Vous avez besoin est un nœud de syntaxe qui représente le nom d’interface et qui sera également utile lors de l’implémentation d’interface est ajoutée à la classe Person. Cela peut être accompli en appelant la méthode NomIdentificateur, qui retourne un nom correct de la chaîne spécifiée :

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

Si vous souhaitez importer le nom qualifié complet, System.ICloneable, vous utiliseriez DottedName au lieu de NomIdentificateur afin de générer un nom qualifié approprié, mais dans l’exemple actuel une NamespaceImportDeclaration pour système a été déjà ajoutée. À ce stade, vous pouvez placer toutes ensemble. SyntaxGenerator présente les méthodes AsPublicInterfaceImplementation et AsPrivateInterfaceImplementation qui vous permet d’indiquer au compilateur qu’une définition de méthode implémente une interface, comme dans l’exemple suivant :

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

Cela est particulièrement important avec Visual Basic, qui requiert explicitement la clause Implements. AsPublicInterfaceImplementation est l’équivalent de l’implémentation d’interface implicite en c#, tandis que AsPrivateInterfaceImplementation est l’équivalent de l’implémentation d’interface explicite. Tous deux fonctionnent avec les méthodes, propriétés et indexeurs.

L’étape suivante est de générer le constructeur, qui est effectué via la méthode ConstructorDeclaration. Comme avec la méthode Clone, définition du constructeur doit être fractionnée en plusieurs parties de comprendre plus facilement et d’un code plus propre. Rappelez-vous de Figure 1 et Figure 2, le constructeur prend deux paramètres de type chaîne, qui sont requis pour l’initialisation des propriétés. Il est judicieux de générer tout d’abord du nœud de syntaxe pour les deux paramètres :

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

Chaque paramètre est généré par la méthode ParameterDeclaration, qui accepte une chaîne représentant le nom du paramètre et une expression qui représente le type de paramètre. Les deux paramètres sont de type chaîne, le code utilise simplement la méthode TypeExpression, comme vous avez déjà appris. Pour les deux paramètres de compression dans un SyntaxNode parce que la ConstructorDeclaration veut un objet de ce type pour représenter les paramètres.

Maintenant, vous devez construire le corps de la méthode, qui tire parti de la méthode AssignmentStatement que vous avez vu précédemment, comme suit :

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

Dans ce cas, il existe deux instructions, à la fois regroupement dans un objet SyntaxNode. Enfin, vous pouvez générer le constructeur, assembler les paramètres et le corps de méthode :

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

ConstructorDeclaration est similaire à MethodDeclaration, mais il est spécifiquement conçue pour générer une méthode .ctor dans c# et une méthode Sub New dans Visual Basic.

Génération d’un CompilationUnit

Jusqu'à présent, vous avez vu comment générer du code pour chaque membre de la classe Person. Vous devez maintenant à réunir ces membres et générer un SyntaxNode approprié pour la classe. Membres de classe doivent être fournis sous la forme d’un SyntaxNode, et ce qui suit montre comment rassembler tous les membres créés précédemment :

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

Maintenant vous pouvez enfin générer la classe Person, tirant parti de la méthode ClassDeclaration comme suit :

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

Comme avec les autres types de déclarations, cette méthode nécessite de spécifier le nom, le type générique (null dans le cas présent), le niveau d’accessibilité, les modificateurs (abstrait dans ce cas), ou MustInherit dans Visual Basic, base de types (nulles dans le cas présent) et les interfaces implémentées (dans ce cas un SyntaxNode contenant le nom d’interface créé précédemment comme nœud de syntaxe). Vous pouvez être amené à encapsuler la classe dans un espace de noms. SyntaxGenerator inclut la méthode NamespaceDeclaration, qui accepte le nom de l’espace de noms et le SyntaxNode qu’il contient. Vous l’utilisez comme suit :

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

Compilateurs savez déjà comment gérer le nœud de syntaxe généré pour l’espace de noms complet et membres imbriqués et comment effectuer une analyse de code sur la syntaxe, mais parfois vous devez renvoyer ce résultat sous la forme d’un CompilationUnit, un type qui représente un fichier de code. Il s’agit des analyseurs de code refactorisations. Voici le code que vous écrivez pour retourner un CompilationUnit :

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

Cette méthode accepte une ou plusieurs instances de SyntaxNode comme argument.

La sortie en c# et Visual Basic

Après tout ce travail, vous êtes prêt à voir le résultat. Figure 5 montre le code c# généré pour la classe Person.

Le langage c# Roslyn Code généré pour la classe Person
Figure 5 le Code c# généré Roslyn pour la classe Person

À présent, simplement changer la langue de Visual Basic dans la ligne de code qui crée un nouveau AdhocWorkspace :

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

Si vous exécutez de nouveau le code, vous obtiendrez une définition de classe Visual Basic, comme dans Figure 6.

Le Code généré par Roslyn de Visual Basic pour la classe Person
Figure 6 le Code généré par Roslyn de Visual Basic pour la classe Person

Le point clé ici est que, avec SyntaxGenerator, vous avez écrit du code une seule fois et ont été en mesure de générer du code c# et Visual Basic, qui permet d’utiliser l’API d’analyse de Roslyn. Lorsque vous avez terminé, n’oubliez pas d’appeler la méthode Dispose sur l’instance AdhocWorkspace ou placez votre code au sein d’un à l’aide de déclaration. Étant donné que personne n’est parfait et le code généré peut contenir des erreurs, vous pouvez également vérifier la propriété ContainsDiagnostics pour voir si les diagnostics dans le code et obtenir des informations détaillées sur les problèmes de code via la méthode GetDiagnostics.

Les analyseurs de langage et les refactorisations

Chaque fois que vous avez besoin réaliser une analyse complète sur le code source, mais cette approche est également très pratique avec les analyseurs et code refactorisations, vous pouvez utiliser APIs Roslyn et la classe SyntaxGenerator. En fait, des analyseurs, des correctifs de code et des refactorisations ont les attributs DiagnosticAnalyzer, ExportCodeFixProvider et ExportCodeRefactoringProvider, respectivement, chacune acceptant les langues prises en charge primaires et secondaires. En utilisant SyntaxGenerator au lieu de SyntaxFactory, vous pouvez cibler simultanément les langages c# et Visual Basic.

Synthèse

La classe SyntaxGenerator à partir de l’espace de noms Microsoft.CodeAnalysis.Editing fournit un moyen indépendant du langage de nœuds de la syntaxe de création, le ciblage de c# et Visual Basic avec une base de code. Avec cette classe puissante, vous pouvez générer tout élément de syntaxe possible d’une manière compatible avec les deux compilateurs, gagner du temps et améliorer la maintenabilité du code.


Alessandro Del Soleest un MVP Microsoft depuis 2008. Promu MVP de l’année cinq fois, il est l’auteur de nombreux livres, livres, des vidéos et articles sur le développement .NET avec Visual Studio. DEL unique fonctionne comme un expert de développeur de solutions pour cerveau-Sys (cerveau-sys.it), la mise au point sur le développement .NET, formation et de Conseil. Vous pouvez le suivre sur Twitter : @progalex.

Remercie les experts techniques Microsoft suivants pour avoir relu cet article : Anthony D. Green et Matt Warren
Anthony D. Green est responsable de programme pour Visual Basic. Anthony a également travaillé sur cette chose Roslyn pendant 5 ans. Il est de Chicago, et vous pouvez le trouver sur Twitter @ThatVBGuy