Was ist Roslyn?
Roslyn ist eine Reihe von Open Source-Compilern und Code-Analyse-APIs für die C # - und VisualBasic .NET-Sprachen von Microsoft.
Der Roslyn-Analysator ist ein leistungsstarkes Tool zum Analysieren von Code, Auffinden und Beheben von Fehlern.
Syntaxbaum und semantisches Modell
Um den Code analysieren zu können, müssen Sie den Syntaxbaum und das semantische Modell verstehen, da dies die beiden Hauptkomponenten für die statische Analyse sind.
Ein Syntaxbaum ist ein Element, das auf der Grundlage des Quellcodes des Programms erstellt wird und für die Codeanalyse erforderlich ist. Während der Analyse des Codes bewegt er sich entlang des Codes.
Jeder Code hat einen Syntaxbaum. Für das nächste Klassenobjekt
class A { void Method() { } }
Der Syntaxbaum sieht folgendermaßen aus:

Ein Objekt vom Typ SyntaxTree ist ein Syntaxbaum. In der Baumstruktur können drei Hauptelemente unterschieden werden: SyntaxNodes, SyntaxTokens, SyntaxTrivia.
Syntaxknoten beschreiben Syntaxkonstrukte, nämlich Deklarationen, Operatoren, Ausdrücke usw. In C # repräsentieren Syntaxkonstrukte eine Klasse vom Typ SyntaxNode.
Syntaxtokens beschreibt Elemente wie: Bezeichner, Schlüsselwörter, Sonderzeichen. In C # handelt es sich um eine Art SyntaxToken-Klasse.
Syntaxtrivia beschreibt Elemente, die nicht kompiliert werden, nämlich Leerzeichen, Zeilenvorschübe, Kommentare und Präprozessoranweisungen. In C # wird es durch eine Klasse vom Typ SyntaxTrivia definiert.
Das semantische Modell repräsentiert Informationen über Objekte und deren Typen. Dank dieses Tools können Sie eine gründliche und komplexe Analyse durchführen. In C # wird es durch eine Klasse vom Typ SemanticModel definiert.
Analysator erstellen
Um einen statischen Analysator zu erstellen, müssen Sie die folgende .NETCompilerPlatformSDK-Komponente installieren.
Die Hauptfunktionen eines Analysegeräts umfassen:
- Registrierung von Aktionen.
Aktionen sind Codeänderungen, die der Analysator initiieren muss, um den Code auf Verstöße zu überprüfen. Wenn VisualStudio Codeänderungen erkennt, die der registrierten Aktion entsprechen, ruft es die registrierte Analysemethode auf. - Diagnose erstellen.
Wenn eine Verletzung erkannt wird, erstellt der Analysator ein Diagnoseobjekt, das von VisualStudio verwendet wird, um den Benutzer über die Verletzung zu benachrichtigen.
Es gibt mehrere Schritte zum Erstellen und Testen eines Analysators:
- Erstellen Sie eine Lösung.
- Registrieren Sie den Namen und die Beschreibung des Analysators.
- Warnungen und Empfehlungen des Berichtsanalysators.
- Führen Sie eine Codekorrektur durch, um die Empfehlungen zu akzeptieren.
- Verbesserung der Analyse durch Unit-Tests.
Aktionen werden in einer Überschreibung der DiagnosticAnalyzer.Initialize-Methode (AnalysisContext) aufgezeichnet, wobei AnalysisContext die Methode ist, mit der die Suche nach dem analysierten Objekt festgelegt wird.
Der Analysator kann eine oder mehrere Codekorrekturen bereitstellen. Ein Code-Patch identifiziert Änderungen, die das gemeldete Problem beheben. Der Benutzer wählt die Änderungen über die Benutzeroberfläche aus (Glühbirnen im Editor), und VisualStudio ändert den Code. Die RegisterCodeFixesAsync-Methode beschreibt, wie der Code geändert wird.
Beispiel
Als Beispiel schreiben wir den Analysator für öffentliche Felder. Diese Anwendung sollte den Benutzer vor öffentlichen Feldern warnen und die Möglichkeit bieten, das Feld mit einer Eigenschaft zu kapseln.
Folgendes sollten Sie bekommen:

Lassen Sie uns herausfinden, was dafür zu tun ist.
Zuerst müssen Sie eine Lösung erstellen.

Nach dem Erstellen der Lösung sehen wir, dass es bereits drei Projekte gibt.

Wir brauchen zwei Klassen:
1) Klasse AnalyzerPublicFieldsAnalyzer, in der wir die Kriterien für die Analyse des Codes zum Auffinden öffentlicher Felder und eine Beschreibung der Warnung für den Benutzer angeben.
Wir geben folgende Eigenschaften an:
public const string DiagnosticId = "PublicField"; private const string Title = "Filed is public"; private const string MessageFormat = "Field '{0}' is public"; private const string Category = "Syntax"; private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
Danach geben wir an, nach welchen Kriterien die Analyse der öffentlichen Felder stattfinden wird.
private static void AnalyzeSymbol(SymbolAnalysisContext context) { var fieldSymbol = context.Symbol as IFieldSymbol; if (fieldSymbol != null && fieldSymbol.DeclaredAccessibility == Accessibility.Public && !fieldSymbol.IsConst && !fieldSymbol.IsAbstract && !fieldSymbol.IsStatic && !fieldSymbol.IsVirtual && !fieldSymbol.IsOverride && !fieldSymbol.IsReadOnly && !fieldSymbol.IsSealed && !fieldSymbol.IsExtern) { var diagnostic = Diagnostic.Create(Rule, fieldSymbol.Locations[0], fieldSymbol.Name); context.ReportDiagnostic(diagnostic); } }
Wir erhalten ein Feld eines Objekts vom Typ IFieldSymbol, das Eigenschaften zum Definieren von Feldmodifikatoren, seines Namens und seiner Position enthält. Was wir für die Diagnose brauchen.
Der Analysator muss noch initialisiert werden, indem in der überschriebenen Methode angegeben wird
public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field); }
2) Nun ändern wir den vom Benutzer vorgeschlagenen Code basierend auf der Analyse des Codes. Dies geschieht in der Klasse AnalyzerPublicFieldsCodeFixProvider.
Geben Sie dazu Folgendes an:
private const string title = "Encapsulate field"; public sealed override ImmutableArray<string> FixableDiagnosticIds { get { return ImmutableArray.Create(AnalyzerPublicFieldsAnalyzer.DiagnosticId); } } public sealed override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var initialToken = root.FindToken(diagnosticSpan.Start); context.RegisterCodeFix( CodeAction.Create(title, c => EncapsulateFieldAsync(context.Document, initialToken, c), AnalyzerPublicFieldsAnalyzer.DiagnosticId), diagnostic); }
Und wir bestimmen die Fähigkeit, das Feld mit einer Eigenschaft in der EncapsulateFieldAsync-Methode zu kapseln.
private async Task<Document> EncapsulateFieldAsync(Document document, SyntaxToken declaration, CancellationToken cancellationToken) { var field = FindAncestorOfType<FieldDeclarationSyntax>(declaration.Parent); var fieldType = field.Declaration.Type; ChangeNameFieldAndNameProperty(declaration.ValueText, out string fieldName, out string propertyName); var fieldDeclaration = CreateFieldDecaration(fieldName, fieldType); var propertyDeclaration = CreatePropertyDecaration(fieldName, propertyName, fieldType); var root = await document.GetSyntaxRootAsync(); var newRoot = root.ReplaceNode(field, new List<SyntaxNode> { fieldDeclaration, propertyDeclaration }); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; }
Erstellen Sie dazu ein privates Feld.
private FieldDeclarationSyntax CreateFieldDecaration(string fieldName, TypeSyntax fieldType) { var variableDeclarationField = SyntaxFactory.VariableDeclaration(fieldType) .AddVariables(SyntaxFactory.VariableDeclarator(fieldName)); return SyntaxFactory.FieldDeclaration(variableDeclarationField) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); }
Erstellen Sie dann eine öffentliche Eigenschaft, die dieses private Feld zurückgibt und empfängt.
private PropertyDeclarationSyntax CreatePropertyDecaration(string fieldName, string propertyName, TypeSyntax propertyType) { var syntaxGet = SyntaxFactory.ParseStatement($"return {fieldName};"); var syntaxSet = SyntaxFactory.ParseStatement($"{fieldName} = value;"); return SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors( SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxGet)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxSet))); }
Gleichzeitig speichern wir den Typ und den Namen des Quellfeldes. Der Name des Feldes lautet wie folgt: "_name" und der Name der Eigenschaft "Name".
Referenzen
- GitHub-Quellen
- Das .NET Compiler Platform SDK