Roslyn als Türöffner für neue Anwendungsszenarien

Roslyn im Entwickleralltag
Kommentare

Microsoft spricht nun schon seit langer Zeit über das Projekt Roslyn, die Neuentwicklung der Compiler für C# und VB. Als erfahrener Entwickler im Microsoft-Umfeld weiß man, dass das jahrelange Verweilen im Preview-Status oft kein gutes Omen ist. Solche Projekte verschwinden oft sang- und klanglos von der Bildfläche. Durch die prominente Platzierung von Roslyn auf der Konferenz Build 2014 scheint es fast sicher, dass Roslyn dieses Schicksal nicht erleiden wird. Doch was bedeutet ein komplett neuer Unterbau Ihres C# Compilers für Sie in der täglichen Praxis?

Sie sind nicht auf die Entwicklung von Visual-Studio-Erweiterungen spezialisiert und denken daher, dass Roslyn für Sie nicht relevant ist? In diesem Artikel möchten wir an einem Beispiel zeigen, dass Roslyn auch Ihnen ganz neue Anwendungsszenarien ermöglichen wird.

Was ist Roslyn?

Roslyn ist der Codename für das Projekt, in dem die Compiler für C# und VB von Grund auf neu gestaltet und entwickelt werden. Seit der Build 2014 ist auch bekannt, dass Microsoft die Ergebnisse von Roslyn Open Source machen wird. Im Gegensatz zu früher sind die Compiler in Roslyn managed und kein native Code mehr. Diese Tatsache mag für die Entwickler in Redmond und für professionelle Compiler-Bauer spannend sein. Als reiner Anwender der Compiler scheint es auf den ersten Blick aber nicht relevant, ob im Hintergrund C++- oder C#-Code arbeitet. Bei näherer Betrachtung ergibt sich jedoch ein anderes Bild. Im Rahmen der Neuentwicklung hat Microsoft nämlich nicht nur die Sprache und die Laufzeitumgebung getauscht. Die gesamte Pipeline für die Übersetzung von C# in Intermediate Language (IL) wurde sauber in Module gegliedert und steht nun als NuGet-Pakete und Klassenbibliotheken zu Verfügung.

Dieser Schritt war für Microsoft selbst unumgänglich, da an mehr und mehr Stellen Teile des C# Compilers notwendig wurden. Denken Sie an IntelliSense oder Code Refactoring in Visual Studio. Um solche Funktionen anbieten zu können, muss die Visual Studio IDE zur Entwicklungszeit C# analysieren können. Mit Roslyn können Compiler und IDE sich die gleichen Komponenten teilen, und die verschiedenen Teams bei Microsoft müssen nicht mehr getrennte Parser warten. Gleiches gilt natürlich auch für Visual-Studio-Erweiterungen von Drittanbietern, für die die Analyse von C#- und VB-Code zur Entwicklungszeit mit Roslyn jetzt ebenfalls ein Kinderspiel ist.

Relevanz für normale Entwickler

Was ist aber mit normalen Entwicklern, die nicht jeden Tag an Visual-Studio-Erweiterungen arbeiten? Roslyn ermöglicht für alle C#- und VB-Entwickler ganz neue Anwendungsszenarien. Analysieren und sogar Verändern von Quellcode zur Entwicklungs- und Laufzeit wird möglich. Lassen Sie uns das anhand eines stark vereinfachten Beispiels durchgehen.Nehmen wir an, Sie entwickeln eine Anwendung für Hausautomatisierung. In diesem Zusammenhang ist ein Modul zur Konfiguration von Sensoren zu schreiben. Für jeden Sensortyp könnte es eine Klasse mit konfigurierbaren Einstellungen geben. Listing 1 zeigt eine solche Klasse namens TemperatureSensorSettings. Achten Sie darauf, dass diese Klasse immutable, also unveränderbar ist. Sie eignet sich daher überhaupt nicht für Data Binding in WPF oder WinRT. Damit das möglich wird, müssen Sie für jede dieser Konfigurationsklassen eine ViewModel-Klasse entwickeln, die mutable, also verändert, ist und das .NET-Interface INotifyPropertyChanged implementiert. Listing 2 zeigt, wie die ViewModel-Klasse aussehen könnte.

// Note that this class is immutable. 
public class TemperatureSensorSettings(double measureFrequency, double accuracy)
{
    public double MeasureFrequency { get; } = measureFrequency;
    public double Accuracy { get; } = accuracy;
}
public class TemperatureSensorSettingsViewModel : INotifyPropertyChanged
{ 
  private double MeasureFrequencyValue;
  double MeasureFrequency
  {
    get { return this.MeasureFrequencyValue; }
    set
    {
      if (this.MeasureFrequencyValue != value)
      {
        this.MeasureFrequencyValue = value;
        this.RaisePropertyChanged("MeasureFrequency");
      }
    }
  }

  private double AccuracyValue;
  double Accuracy
  {
    get { return this.AccuracyValue; }
    set
    {
      if (this.AccuracyValue != value)
      {
        this.AccuracyValue = value;
        this.RaisePropertyChanged("Accuracy");
      }
    }
  }

  internal TemperatureSensorSettings TemperatureSensorSettings
  {
    get 
    { 
      return new TemperatureSensorSettings(
        MeasureFrequency, Accuracy);
    }
  }

  public void RaisePropertyChanged(string propertyName)
  {
    if (this.PropertyChanged != null)
    {
      this.PropertyChanged(this, 
        new PropertyChangedEventArgs(propertyName));
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

Das manuelle Erstellen solcher ViewModel-Klassen wäre zeitaufwändig und fehleranfällig. Jede Änderung an den zugrunde liegenden Einstellungsklassen würde zwangsläufig zu einer notwendigen Änderung in den ViewModel-Klassen führen. Diese Arbeit wäre weder besonders spannend noch effizient. Wie löst man dieses Problem?

Codegenerierung

Es liegt auf der Hand, dass die ViewModel-Klassen in unserem Beispiel generiert werden könnten. Ohne Roslyn wäre dies jedoch schwierig, denn der Ausgangspunkt für die Generierung ist C#-Quellcode. Aus diesem Grund wurden bisher üblicherweise andere Lösungswege für solche Szenarien gewählt. Hier einige Beispiele:

  • Beschreibung des Modells in einem leicht zu verarbeitenden Textformat wie XML, XAML oder JSON. Daraus können mit Template Engines wie T4 oder String Template sowohl Model– als auch ViewModel-Klassen generiert werden. Der Nachteil: Das Beschreibungsformat muss individuell erfunden werden und ist im Regelfall gegenüber vollwertigem C# stark eingeschränkt.
  • Generierung der ViewModel-Klassen zur Laufzeit mithilfe von Reflection. Die Model-Klassen werden dabei mit Reflection zur Laufzeit analysiert und anschließend werden dynamisch beispielsweise mit Reflection.Emit ViewModel-Klassen erzeugt. Der Nachteil: Schwierig zu entwickeln, fehleranfällig und relativ langsam, da die Codegenerierung zur Laufzeit geschieht.
  • Man könnte auf Drittanbieterkomponenten wie beispielsweise AOP (aspektorientierte Programmierung) Frameworks wie PostSharp zurückgreifen.

[ header = Seite 2: Parser und Syntax Tree ]

Parser und Syntax Tree

Roslyn ändert die Voraussetzungen in unserem Beispiel komplett. Mithilfe der Parser in der Roslyn-Bibliothek ist die Analyse von C#- und VB-Quellcode so einfach wie das Verarbeiten von XML oder JSON. Es genügt eine einzige Methode (CSharpSyntaxTree.ParseText), um Quellcode in einen Objektbaum (Syntax Tree) im Speicher umzuwandeln. Dieser Objektbaum kann dann verwendet werden, um ihn in ein anderes Format umzuwandeln.

Lassen Sie uns das an unserem einfachen Beispiel probieren. Falls Sie selbst experimentieren möchten, erstellen Sie ein Projekt in Visual Studio und fügen Sie das Roslyn-NuGet-Paket Microsoft.CodeAnalysis hinzu. Achtung: Das Package ist noch in Preview. Sie müssen daher beim Hinzufügen in Visual Studio Include Prerelease auswählen. Unser Ziel ist die automatische Umwandlung einer einfachen Model-Klasse (Listing 1) in eine ViewModel-Klasse (Listing 2). Für das Parsen reicht der Aufruf der Methode CSharpSyntaxTree.ParseText. Als Ergebnis erhält man eine Objektstruktur, die den Syntax Tree unserer Model-Klasse abbildet. Zum interaktiven Erkunden des Syntax Tree fügt Roslyn das Tool Roslyn Syntax Visualizer zu Visual Studio hinzu. Sie erhalten es, wenn Sie die Roslyn End User Preview auf Ihrem Computer installieren (kann hier heruntergeladen werden). Abbildung 1 zeigt den Visualizer für unsere Model-Klasse.

Abb. 1: Roslyn Syntax Visualizer

Roslyn und T4

Hat man die Quelle einmal als Objektbaum im Speicher, steht der Generierung von Code nichts mehr im Weg. In unserem Beispiel verwenden wir die in Visual Studio eingebaute Template Engine T4. Listing 3 zeigt das Template, mit dem aus der Model-Klasse aus Listing 1 die ViewModel-Klasse aus Listing 2 automatisch generiert wird. Die Roslyn-bezogenen Klassen und Methoden sind fett hervorgehoben. Achten Sie darauf, wie der in Abbildung 1 sichtbare Syntax Tree mit den Klassennamen im T4 Template korrespondiert. Das erleichtert speziell am Anfang den Umgang mit den Roslyn-Klassen ungemein.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Collections.Immutable.dll" #>
<#@ assembly name="Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="System.Runtime" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
using System.ComponentModel;

namespace RoslynCodeGenerationSample
{
<# 
// Read class source
var solutionPath = Host.ResolveAssemblyReference("$(SolutionDir)");
var classSource = File.ReadAllText(Path.Combine(
  solutionPath, "TemperatureSensorSettings.cs"));

// Parse source using Roslyn
var tree = CSharpSyntaxTree.ParseText(classSource)
  .GetRoot() as CompilationUnitSyntax;

// Navigate to class inside namespace
var namespaceDeclaration = tree.Members[0] as NamespaceDeclarationSyntax;
var immutableClass = namespaceDeclaration.Members[0] as ClassDeclarationSyntax;
#>
  public class <#= immutableClass.Identifier.Text #>ViewModel 
    : INotifyPropertyChanged
  { 
<# foreach(var property in immutableClass.Members
  .OfType())
  { #>
    private <#= ((PredefinedTypeSyntax)property.Type).Keyword.Text #> 
      <#= property.Identifier.Text #>Value;
    <#= ((PredefinedTypeSyntax)property.Type).Keyword.Text #> 
      <#= property.Identifier.Text #>
    {
      get { return this.<#= property.Identifier.Text #>Value; }
      set
      {
        if (this.<#= property.Identifier.Text #>Value != value)
        {
          this.<#= property.Identifier.Text #>Value = value;
          this.RaisePropertyChanged(
            "<#= property.Identifier.Text #>");
        }
      }
    }

<# } #>
    internal <#= immutableClass.Identifier.Text #> 
      <#= immutableClass.Identifier.Text #>
    {
      get 
      { 
        return new <#= immutableClass.Identifier.Text #>(<#= string.Join(
          ", ", 
          immutableClass.Members
            .OfType()
            .Select(p => p.Identifier.Text)) #>);
      }
    }

    public void RaisePropertyChanged(string propertyName) { [...] }
    public event PropertyChangedEventHandler PropertyChanged;
  }
}

Fazit

C#- und VB-Code in einem eigenen Programm zu analysieren und zu verarbeiten, war früher ohne Roslyn für die meisten Entwickler undenkbar. Roslyn macht diese Aufgabe kinderleicht. Obwohl stark vereinfacht, zeigt das Beispiel in diesem Artikel hoffentlich, dass es für Roslyn nicht nur für Visual-Studio-Add-in-Entwickler Einsatzszenarien gibt. Roslyn hat für die tägliche Programmierarbeit das Potenzial, Routineaufgaben, die Ihr Team heute manuell erledigt, zu automatisieren. Codegenerierung, größere Refactoring-Aufgaben, Analyse des Quellcodes auf Best und Worst Practices, alles Beispiele, bei denen Roslyn helfen kann. Auf diese Weise gewinnen wir Zeit, die wir besser investieren können als darin, mechanisch die gleichen Aufgaben wieder und wieder durchzuführen.

Roslyn ist zwar noch im Preview-Stadium, in Szenarien wie dem hier gezeigten können Sie jedoch schon heute davon profitieren. Wenn Sie Roslyn zur Entwicklungszeit zum Generieren von Code verwenden, müssen Sie zur Laufzeit keinerlei Bibliotheken von Roslyn ausliefern. Ihre Software benötigt auf Kundenseite also keine Komponenten, die nicht voll von Microsoft unterstützt sind.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -