Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Deze zelfstudie bouwt voort op concepten en technieken die worden verkend in de quickstarts Aan de slag met syntaxisanalyse en Aan de slag met semantische analyse. Als u dat nog niet hebt gedaan, moet u deze quickstarts voltooien voordat u deze start.
In deze quickstart verkent u technieken voor het maken en transformeren van syntaxisstructuren. In combinatie met de technieken die u in vorige quickstarts hebt geleerd, maakt u uw eerste opdrachtregelherstructurering!
Installatie-instructies - Visual Studio Installer
Er zijn twee verschillende manieren om de .NET Compiler Platform SDK te vinden in het Installatieprogramma van Visual Studio:
Installeren met behulp van het installatieprogramma van Visual Studio - Workloads-weergave
De .NET Compiler Platform SDK wordt niet automatisch geselecteerd als onderdeel van de ontwikkelworkload van de Visual Studio-extensie. U moet het selecteren als een optioneel onderdeel.
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Controleer de ontwikkelworkload van de Visual Studio-extensie .
- Open het ontwikkelknooppunt van de Visual Studio-extensie in de overzichtsstructuur.
- Zorg ervoor dat het selectievakje voor .NET Compiler Platform SDK is ingeschakeld.
- Selecteer Wijzigen.
Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in de visualisatie:
- Open het knooppunt Afzonderlijke onderdelen in de overzichtsstructuur.
- Schakel het selectievakje voor DGML-editor in
Installeren met behulp van het tabblad Afzonderlijke onderdelen van Visual Studio Installer
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Het tabblad Afzonderlijke onderdelen selecteren
- Schakel het selectievakje voor .NET Compiler Platform SDK in. U vindt het bovenaan in de sectie Compilers, buildhulpprogramma's en runtimes.
- Selecteer Wijzigen.
Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in de visualisatie:
- Schakel het selectievakje voor DGML-editor in. U vindt deze in de sectie Codehulpprogramma's .
Onveranderbaarheid en het .NET-compilerplatform
Onveranderbaarheid is een fundamenteel tenet van het .NET-compilerplatform. Onveranderbare gegevensstructuren kunnen niet worden gewijzigd nadat ze zijn gemaakt. Onveranderbare gegevensstructuren kunnen veilig worden gedeeld en geanalyseerd door meerdere consumenten tegelijk. Er is geen gevaar dat de ene consument een andere op onvoorspelbare manieren beïnvloedt. Uw analyse heeft geen vergrendelingen of andere gelijktijdigheidsmetingen nodig. Deze regel is van toepassing op syntaxisstructuren, compilaties, symbolen, semantische modellen en elke andere gegevensstructuur die u tegenkomt. In plaats van bestaande structuren te wijzigen, maken API's nieuwe objecten op basis van opgegeven verschillen in de oude. U past dit concept toe op syntaxisstructuren om nieuwe bomen te maken met behulp van transformaties.
Bomen maken en transformeren
U kiest een van de twee strategieën voor syntaxistransformaties. Factory-methoden worden het beste gebruikt wanneer u zoekt naar specifieke knooppunten die u wilt vervangen of specifieke locaties waar u nieuwe code wilt invoegen. Rewriters zijn het beste wanneer u een heel project wilt scannen op codepatronen die u wilt vervangen.
Knooppunten maken met factory-methoden
De eerste syntaxistransformatie demonstreert de factory-methoden. U gaat een using System.Collections; instructie vervangen door een using System.Collections.Generic; instructie. In dit voorbeeld ziet u hoe u objecten maakt Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode met behulp van de Microsoft.CodeAnalysis.CSharp.SyntaxFactory factory-methoden. Voor elk type knooppunt, token of trivia is er een factorymethode waarmee een exemplaar van dat type wordt gemaakt. U maakt syntaxisstructuren door knooppunten hiërarchisch op te stellen op een bottom-up manier. Vervolgens transformeert u het bestaande programma door bestaande knooppunten te vervangen door de nieuwe structuur die u hebt gemaakt.
Start Visual Studio en maak een nieuw C# Stand-Alone Code Analysis Tool-project . Kies in Visual Studio Bestand>nieuw>project om het dialoogvenster Nieuw project weer te geven. Kies onder Visual C#>Extensibility een Stand-Alone Code Analysis Tool. Deze quickstart heeft twee voorbeeldprojecten, dus noem de oplossing SyntaxTransformationQuickStart en noem het project ConstructionCS. Klik op OK.
In dit project worden de Microsoft.CodeAnalysis.CSharp.SyntaxFactory klassemethoden gebruikt om een Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax representatie van de System.Collections.Generic naamruimte te maken.
Voeg de volgende using instructie toe aan de bovenkant van de Program.cs.
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static System.Console;
U maakt naamsyntaxisknooppunten om de structuur te bouwen die de using System.Collections.Generic; instructie vertegenwoordigt.
NameSyntax is de basisklasse voor vier typen namen die worden weergegeven in C#. U stelt deze vier typen namen samen om elke naam te maken die in de C#-taal kan worden weergegeven:
-
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax, die eenvoudige namen van één id vertegenwoordigt, zoals
SystemenMicrosoft. -
Microsoft.CodeAnalysis.CSharp.Syntax.GenericNameSyntax, dat een algemene type of methodenaam vertegenwoordigt, zoals
List<int>. -
Microsoft.CodeAnalysis.CSharp.Syntax.QualifiedNameSyntax, die een gekwalificeerde naam van het formulier vertegenwoordigt
<left-name>.<right-identifier-or-generic-name>, zoalsSystem.IO. -
Microsoft.CodeAnalysis.CSharp.Syntax.AliasQualifiedNameSyntax, die een naam vertegenwoordigt met behulp van een assembly extern alias, zoals
LibraryV2::Foo.
U gebruikt de IdentifierName(String) methode om een NameSyntax knooppunt te maken. Voeg de volgende code toe aan uw Main methode in Program.cs:
NameSyntax name = IdentifierName("System");
WriteLine($"\tCreated the identifier {name}");
Met de voorgaande code wordt een IdentifierNameSyntax object gemaakt en toegewezen aan de variabele name. Veel van de Roslyn-API's retourneren basisklassen om het gemakkelijker te maken om met gerelateerde typen te werken. De variabele name, een NameSyntax, kan opnieuw worden gebruikt tijdens het bouwen van de QualifiedNameSyntax. Gebruik geen typedeductie terwijl u het voorbeeld bouwt. U automatiseert die stap in dit project.
U hebt de naam gemaakt. Nu is het tijd om meer knooppunten aan de boom toe te voegen door een QualifiedNameSyntax te bouwen. De nieuwe structuur gebruikt name als de linkerkant van de naam en een nieuwe IdentifierNameSyntax voor de Collections-naamruimte als de rechterkant van de QualifiedNameSyntax. Voeg de volgende code toe aan program.cs:
name = QualifiedName(name, IdentifierName("Collections"));
WriteLine(name.ToString());
Voer de code opnieuw uit en bekijk de resultaten. U bouwt een structuur met knooppunten die code vertegenwoordigt. U gaat dit patroon voortzetten om de QualifiedNameSyntax naamruimte System.Collections.Genericte bouwen. Voeg de volgende code toe aan Program.cs:
name = QualifiedName(name, IdentifierName("Generic"));
WriteLine(name.ToString());
Voer het programma opnieuw uit om te zien dat u de structuur voor de code hebt gemaakt die u wilt toevoegen.
Een gewijzigde boom maken
U hebt een kleine syntaxisstructuur gemaakt die één instructie bevat. De API's voor het maken van nieuwe knooppunten zijn de juiste keuze om afzonderlijke instructies of andere kleine codeblokken te maken. Als u echter grotere codeblokken wilt bouwen, moet u methoden gebruiken die knooppunten vervangen of knooppunten invoegen in een bestaande structuur. Houd er rekening mee dat syntaxisstructuren onveranderbaar zijn. De syntaxis-API biedt geen mechanisme voor het wijzigen van een bestaande syntaxisstructuur na de constructie. In plaats daarvan biedt het methoden waarmee nieuwe bomen worden geproduceerd op basis van wijzigingen in bestaande bomen.
With* methoden worden gedefinieerd in concrete klassen die zijn afgeleid van SyntaxNode of in extensieleden die in de SyntaxNodeExtensions klasse zijn gedeclareerd. Met deze methoden maakt u een nieuw knooppunt door wijzigingen toe te passen op de onderliggende eigenschappen van een bestaand knooppunt. Daarnaast kan het ReplaceNode uitbreidingslid worden gebruikt om een afstammend knooppunt in een substructuur te vervangen. Deze methode werkt ook de ouder bij zodat deze verwijst naar het zojuist gemaakte kind en herhaalt dit proces in de hele boomstructuur, een proces dat bekend staat als het herstructureren van de boom.
De volgende stap is het maken van een boomstructuur die een volledig (klein) programma vertegenwoordigt en deze vervolgens wijzigt. Voeg de volgende code toe aan het begin van de Program klasse:
private const string sampleCode =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
Opmerking
De voorbeeldcode gebruikt de System.Collections naamruimte en niet de System.Collections.Generic naamruimte.
Voeg vervolgens de volgende code toe aan de onderkant van de Main methode om de tekst te parseren en een structuur te maken:
SyntaxTree tree = CSharpSyntaxTree.ParseText(sampleCode);
var root = (CompilationUnitSyntax)tree.GetRoot();
In dit voorbeeld wordt de WithName(NameSyntax) methode gebruikt om de naam in een UsingDirectiveSyntax knooppunt te vervangen door de naam die in de voorgaande code is samengesteld.
Maak een nieuw UsingDirectiveSyntax knooppunt met behulp van de WithName(NameSyntax) methode om de System.Collections naam bij te werken met de naam die u in de voorgaande code hebt gemaakt. Voeg de volgende code toe aan de onderkant van de Main methode:
var oldUsing = root.Usings[1];
var newUsing = oldUsing.WithName(name);
WriteLine(root.ToString());
Voer het programma uit en bekijk de uitvoer zorgvuldig. De newUsing is niet in de hoofdstructuur geplaatst. De oorspronkelijke boom is niet gewijzigd.
Voeg de volgende code toe met behulp van de ReplaceNode uitbreidingsmethode om een nieuwe structuur te maken. De nieuwe boomstructuur is het resultaat van het vervangen van de bestaande import door het bijgewerkte newUsing knooppunt. Je wijst deze nieuwe boom toe aan de bestaande root variabele.
root = root.ReplaceNode(oldUsing, newUsing);
WriteLine(root.ToString());
Voer het programma opnieuw uit. Deze keer importeert de boom de System.Collections.Generic naamruimte correct.
Bomen transformeren met behulp van SyntaxRewriters
De With* en ReplaceNode methoden bieden handige middelen om afzonderlijke vertakkingen van een syntaxisstructuur te transformeren. De Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter klasse voert meerdere transformaties uit op een syntaxisstructuur. De Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter klasse is een subklasse van Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>. De CSharpSyntaxRewriter past een transformatie toe op een specifiek type SyntaxNode. U kunt transformaties toepassen op meerdere typen SyntaxNode objecten, waar ze ook in een syntaxisstructuur worden weergegeven. Met het tweede project in deze quickstart maakt u een refactoring van de opdrachtregel waarmee expliciete typen in lokale variabeledeclaraties worden verwijderd, overal waar type-inferentie kan worden gebruikt.
Maak een nieuw C# Stand-Alone Code Analysis Tool-project . Klik in Visual Studio met de rechtermuisknop op het SyntaxTransformationQuickStart oplossingsknooppunt. KiesNieuw project> om het dialoogvenster Nieuw project weer te geven. Kies onder Visual C#>ExtensibilityStand-Alone Code Analysis Tool. Geef uw project TransformationCS een naam en klik op OK.
De eerste stap is het maken van een klasse die is afgeleid van CSharpSyntaxRewriter om transformaties uit te voeren. Voeg een nieuw klassebestand toe aan het project. Kies in Visual Studio Project>Add Class.... Typ in het dialoogvenster TypeInferenceRewriter.cs als bestandsnaam.
Voeg de volgende using instructies toe aan het TypeInferenceRewriter.cs bestand:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Maak vervolgens dat de TypeInferenceRewriter klasse de CSharpSyntaxRewriter klasse uitbreidt:
public class TypeInferenceRewriter : CSharpSyntaxRewriter
Voeg de volgende code toe om een privé-alleen-lezenveld te declareren dat een SemanticModel bewaart en initialiseer het in de constructor. U hebt dit veld later nodig om te bepalen waar typedeductie kan worden gebruikt:
private readonly SemanticModel SemanticModel;
public TypeInferenceRewriter(SemanticModel semanticModel) => SemanticModel = semanticModel;
Overschrijf de VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax) methode:
public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
}
Opmerking
Veel van de Roslyn-API's declareren retourtypen die basisklassen zijn van de werkelijke runtimetypen die worden geretourneerd. In veel scenario's kan één soort knooppunt worden vervangen door een ander type knooppunt, of zelfs verwijderd. In dit voorbeeld retourneert de VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax) methode een SyntaxNode, in plaats van het afgeleide type LocalDeclarationStatementSyntax. Deze rewriter retourneert een nieuw LocalDeclarationStatementSyntax knooppunt op basis van de bestaande.
In deze quickstart worden declaraties van lokale variabelen verwerkt. U kunt deze uitbreiden naar andere declaraties, zoals foreach lussen, for lussen, LINQ-expressies en lambda-expressies. Verder zal deze rewriter alleen declaraties van de eenvoudigste vorm transformeren:
Type variable = expression;
Als u zelf wilt verkennen, kunt u overwegen het voltooide voorbeeld voor deze typen variabeledeclaraties uit te breiden:
// Multiple variables in a single declaration.
Type variable1 = expression1,
variable2 = expression2;
// No initializer.
Type variable;
Voeg de volgende code toe aan de hoofdtekst van de VisitLocalDeclarationStatement methode om deze vormen van declaraties over te slaan:
if (node.Declaration.Variables.Count > 1)
{
return node;
}
if (node.Declaration.Variables[0].Initializer == null)
{
return node;
}
De methode geeft aan dat er geen herschrijven plaatsvindt door de node parameter ongewijzigd te retourneren. Als geen van deze if expressies waar is, vertegenwoordigt het knooppunt een mogelijke declaratie met initialisatie. Voeg deze instructies toe om de typenaam op te halen die is opgegeven in de declaratie en deze te binden met behulp van het SemanticModel veld om een typesymbool te verkrijgen:
var declarator = node.Declaration.Variables.First();
var variableTypeName = node.Declaration.Type;
var variableType = (ITypeSymbol)SemanticModel
.GetSymbolInfo(variableTypeName)
.Symbol;
Voeg nu deze instructie toe om de initialisatie-expressie te binden:
var initializerInfo = SemanticModel.GetTypeInfo(declarator.Initializer.Value);
Voeg ten slotte de volgende if instructie toe om de bestaande typenaam te vervangen door het var trefwoord als het type van de initializer-expressie overeenkomt met het opgegeven type:
if (SymbolEqualityComparer.Default.Equals(variableType, initializerInfo.Type))
{
TypeSyntax varTypeName = SyntaxFactory.IdentifierName("var")
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
return node.ReplaceNode(variableTypeName, varTypeName);
}
else
{
return node;
}
De voorwaarde is vereist omdat de declaratie de initialisatie-expressie kan casten naar een basisklasse of interface. Als dat gewenst is, komen de typen aan de linkerkant en de rechterzijde van de opdracht niet overeen. Als u het expliciete type in deze gevallen verwijdert, wordt de semantiek van een programma gewijzigd.
var wordt opgegeven als een id in plaats van een trefwoord, omdat var dit een contextueel trefwoord is. De voorafgaande en volgende trivia (witruimte) worden overgebracht van de oude typenaam naar het var trefwoord om verticale witruimtes en inspringing te behouden. Het is eenvoudiger om ReplaceNode te gebruiken in plaats van With* om de LocalDeclarationStatementSyntax te transformeren, omdat de typenaam eigenlijk een subtype van de declaratie-instructie is.
Je hebt de TypeInferenceRewriter voltooid. Ga nu terug naar het Program.cs bestand om het voorbeeld te voltooien. Maak een test Compilation en haal het SemanticModel op. Gebruik die SemanticModel om uw TypeInferenceRewriter uit te proberen. U voert deze stap als laatste uit. Declareer ondertussen een tijdelijke aanduidingsvariabele die uw testcompilatie vertegenwoordigt:
Compilation test = CreateTestCompilation();
Nadat u een ogenblik hebt gepauzeerd, ziet u dat er een foutonderschrijving verschijnt die aangeeft dat er geen CreateTestCompilation methode bestaat. Druk op Ctrl+Punt om de gloeilamp te openen en druk vervolgens op Enter om de opdracht Methode-stub genereren aan te roepen. Met deze opdracht wordt een methode-stub gegenereerd voor de CreateTestCompilation methode in de Program klasse. U komt later terug om deze methode in te vullen:
Schrijf de volgende code om over elk SyntaxTree in de test Compilation te itereren. Initialiseer voor elk element een nieuw TypeInferenceRewriter met de SemanticModel voor die boom.
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
SemanticModel model = test.GetSemanticModel(sourceTree);
TypeInferenceRewriter rewriter = new TypeInferenceRewriter(model);
SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}
Voeg in de foreach instructie die u hebt gemaakt, de volgende code toe om de transformatie uit te voeren op elke bronstructuur. Met deze code wordt de nieuwe getransformeerde structuur voorwaardelijk weggeschreven als er wijzigingen zijn aangebracht. Uw rewriter mag alleen een boom wijzigen als deze een of meer lokale variabeledeclaraties tegenkomt die kunnen worden vereenvoudigd met behulp van type-inferentie.
SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
U ziet kronkels onder de File.WriteAllText code. Selecteer de gloeilamp en voeg de benodigde using System.IO; instructie toe.
U bent bijna klaar. Er is nog één stap over: een test maken Compilation. Omdat u tijdens deze quickstart helemaal geen typedeductie hebt gebruikt, zou het een perfecte testcase hebben gemaakt. Helaas valt het maken van een compilatie vanuit een C#-projectbestand buiten het bereik van dit scenario. Maar gelukkig, als je zorgvuldig instructies hebt gevolgd, is er hoop. Vervang de inhoud van de CreateTestCompilation methode door de volgende code. Er wordt een testcompilatie gemaakt die toevallig overeenkomt met het project dat in deze quickstart wordt beschreven:
String programPath = @"..\..\..\Program.cs";
String programText = File.ReadAllText(programPath);
SyntaxTree programTree =
CSharpSyntaxTree.ParseText(programText)
.WithFilePath(programPath);
String rewriterPath = @"..\..\..\TypeInferenceRewriter.cs";
String rewriterText = File.ReadAllText(rewriterPath);
SyntaxTree rewriterTree =
CSharpSyntaxTree.ParseText(rewriterText)
.WithFilePath(rewriterPath);
SyntaxTree[] sourceTrees = { programTree, rewriterTree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
Steek uw vingers over en voer het project uit. Kies in Visual Studio Debug>Start met Debuggen. U zou een melding moeten krijgen van Visual Studio dat de bestanden in uw project gewijzigd zijn. Klik op Ja op Alles om de gewijzigde bestanden opnieuw te laden. Onderzoek ze om te zien hoe geweldig je bent. Let op hoe veel schoner de code eruitziet zonder al die expliciete en redundante typeaanduidingen.
Gefeliciteerd! U hebt de Compiler-API's gebruikt om uw eigen herstructurering te schrijven die alle bestanden in een C#-project doorzoekt op bepaalde syntactische patronen, de semantiek van broncode analyseert die overeenkomt met deze patronen en transformeert deze. Je bent nu officieel een refactoring-auteur!