
Kevin Erath
Geschäftsführer
Veröffentlicht am
20. März 2021

Ziel des heutigen Posts ist es anhand einer kleinen Aufgabe das Verwenden des Syntaxbaums von Roslyn zu zeigen. Der Syntaxbaum ist eine Repräsentation des Quellcodes, in welchem alle verwendeten Schlüsselwörter hierarchisch abgelegt sind. Näheres zum Syntaxbaum kann in diesem Whitepaper nachgelesen werden.
Als Aufgabe soll eine kleine Konsolenanwendung erstellt werden, welche die geworfenen Ausnahmen innerhalb eines Quellcodes auflistet. Damit ist gemeint, dass eine Visual Studio Solution oder ein Visual Studio Projekt geöffnet werden kann und alle Methoden aufgelistet werden die eine Ausnahme werfen. Bei der Ausgabe soll neben der Methode auch die dazugehörige Klasse ausgegeben werden.
Für folgenden Code sollte dann die nachfolgend aufgeführte Ausgabe generiert werden:
using System;
class KlasseA
{
public void Methode1()
{
}
public void Methode2()
{
throw new NotImplementedException();
}
}
class KlasseB
{
public void Methode1()
{
if (1 != 2)
{
throw new Exception();
}
}
}
KlasseA
– Methode2
– NotImplementedException
KlasseB
– Methode1
– Exception
Was muss das Tool nun tun? Nach kurzem Überlegen fallen mir folgende Schritte ein:
- C#-Dateien (
*.cs
) für Solution/Projekt ermitteln - Die in den C#-Dateien enthaltenen Klassen ermitteln
- Alle Methoden der Klassen ermitteln
- Alle Ausnahmen pro Methode ermitteln
- Ergebnis auf dem Bildschirm ausgeben
Aus diesen Schritten lässt sich folgendes Flow Design ableiten:

Flow Design für Exception Lister
Damit wir mit Roslyn eine Visual Studio Solution oder ein Visual Studio Projekt laden können, benötigen wir eine Instanz der Klasse MSBuildWorkspace
. Diese kann über die statische Methode Create()
erzeugt werden.
private static MSBuildWorkspace CreateWorkspace()
{
return MSBuildWorkspace.Create();
}
Eine Solution kann dann über die Methode OpenSolutionAsync()
geladen werden. Mit der Methode OpenProjectAsync()
hingegen kann ein einzelnes Projekt geladen werden. Die hier vorgestellte Lösung kann mit beidem umgehen. Die Unterscheidung wird dabei allerdings nur anhand der Dateinamenserweiterung gemacht. Zu beachten dabei ist, dass beide Methoden nur als asynchrone Variante zur Verfügung stehen. Das ist aus meiner Sicht positiv, das Task-based Asynchronous Pattern (TAP) zieht sich durch die komplette API von Roslyn. Hier die Implementierung der Operation LoadProjectsFromFile()
:
private static IEnumerable<Project> LoadProjectsFromFile(
string filename, MSBuildWorkspace workspace)
{
if (Path.GetExtension(filename) == ".sln")
{
var solution = workspace.OpenSolutionAsync(filename).Result;
return solution.Projects;
}
var project = workspace.OpenProjectAsync(filename).Result;
return new List<Project> {project};
}
Auf die einzelnen Dokumente, in diesem Fall *.cs-Dateien, kann dann für jedes Projekt über die Eigenschaft Documents
zugegriffen werden. Um den jeweiligen C#-Code eines Dokuments programmatisch zu verarbeiten, muss die syntaktische Analyse durchgeführt werden. Dies geschieht über die asynchrone Methode GetSyntaxRootAsync()
. Als Ergebnis erhält man den Rootknoten des Syntaxbaums. Dieser Baum enthält alle Elemente der C#-Datei.
private static IEnumerable<DocumentInfo> OpenAllDocuments(
IEnumerable<Project> projects)
{
var documents = projects.SelectMany(x => x.Documents);
return documents.Select(x => new DocumentInfo {
Node = x.GetSyntaxRootAsync().Result});
}
Nachdem wir nun eine Auflistung aller Syntaxbäume haben, können wir mithilfe dieser, alle Klassen dies sich im Sourcecode befinden ermitteln. Dies geschieht am einfachsten, wenn die Methode DescendantNodes()
in Kombination mit der LINQ-Methode OfType<ClassDeclarationSyntax>()
verwendet wird. DescendantNodes()
liefert dabei eine flache Liste aller Elemente im Syntaxbaum zurück, dies hat den großen Vorteil, dass man leicht nach Elementen Suchen/Filtern kann. Leider birgt dies unter Umständen den Nachteil, dass es aus Performanzgesichtspunkten nicht die eleganteste Art ist, da der komplette Bauminhalt verarbeitet wird. Dennoch ist dies, gerade durch LINQ, eine sehr einfache Art um nach bestimmten Knoten zu Suchen.
private static IEnumerable<ClassInfo> FindClasses(
IEnumerable<DocumentInfo> documents)
{
return documents.SelectMany(
x => x.Node.DescendantNodes().OfType<ClassDeclarationSyntax>().Select(
y => new ClassInfo {Document = x, Node = y}));
}
Das anschließende Finden der Methoden und Ausnahmen funktioniert analog zum Ermitteln der Klassen. Wobei in diesem Falle dann nach MethodDeclarationSyntax
bzw. ThrowStatementSyntax
gefiltert wird. Abschließend müssen die gefundenen Ausnahmen nur noch auf dem Bildschirm ausgegeben werden. Voilà, wir haben unser Kommandozeilentool, welches die anfangs gestellten Anforderungen erfüllt. Der komplette Sourcecode kann auf GitHub eingesehen werden.
Wer sich jetzt wundert, woher man Informationen zum Aufbau des Syntaxbaums bekommen kann, der sollte sich den Roslyn Syntax Visualizer anschauen. Welcher als Erweiterung für Visual Studio zur Verfügung steht. Mit ihm kann man beliebigen Source Code innerhalb des Visual Studios analysieren. Die Erweiterung kann über die Visual Studio Galerie installiert werden. Hier ein Beispiel, dass den Sourcecode von oben visualisiert:

Roslyn Syntax Visualizer
Ich hoffe ich konnte mit dieser kleinen Anwendung nicht nur ein weiteres Beispiel für Flow Design aufzeigen, sondern auch den Syntaxbaum von Roslyn etwas näher zeigen. Wer sich die Anwendung genauer ansieht, wird aber auch feststellen, dass die aktuelle Lösung nicht den komplette Namen (inkl. Namensraum) für die Ausnahmen ermittelt. Um dies zu ermöglichen, muss auf das semantische Modell zurückgegriffen werden, dass ich in einem späteren Blogpost zeigen möchte.
Anmerkung: Bei diesem Text handelt es sich um einen überarbeiteten Repost eines alten Blog-Artikels aus 2015 von mir.

Hier schreibt
Kevin Erath
Als Mitbegründer und Geschäftsführer von pep.digital verbringe ich zwar nicht mehr jeden Tag ausschließlich damit, coole Lösungen für unsere Kunden zu realisieren. Trotzdem finde ich immer wieder die Zeit, mich auch mal tiefer in die Technik einzutauchen und meine Erkenntnisse hier im Blog zu teilen. Und ehrlich gesagt, das Unternehmen und unsere tollen Mitarbeiter:innen weiterzuentwickeln, macht mir mindestens genauso viel Spaß.
Quellen
Weitere interessante Artikel
Wir möchten hier nicht nur über Neuigkeiten aus dem Unternehmen berichten, sondern auch das Wissen und die Erfahrung unserer Experten teilen.

Wie helfen Liberating Structures in meinem Scrum Projekt?
Meetings und Workshops, in denen die Teilnehmer aktiv mitarbeiten und in denen das Wissen und die Fähigkeiten aller Teilnehmer genutzt werden. Klingt das gut?

Karin Neubauer
Business Analystin

Rekursiv oder iterativ?
Algorithmen lassen sich oft mit einer Rekursion lösen. Mithilfe von yield und Flow Design kann diese Rekursion in mehrere unabhängige Methoden zerlegt werden.

Kevin Erath
Geschäftsführer