Interne DSL in C#

Vor einiger Zeit habe ich bereits einen Post über Chaining in .NET mit Fluent Interfaces geschrieben, spielt man diesen Gedanken etwas weiter, ist man schnell bei der Realisierung einer internen DSL.

Eine interne DSL bietet dem Entwickler eine fließende API. Fließend bedeutet in diesem Zusammenhang, dass Methodenaufrufe hintereinander gereiht werden können und erst nach dem letzten Call das Objekt weiterverarbeitet wird. Eine interne DSL erstellt man durch die Verwendung  von MethodChaining.

 

Eines der besten Beispiele für konsequentes MethodChaining bietet RhinoMocks. Bei RhinoMocks wurde durchgängig auf MethodChaining gesetzt, was in eine einfache, verständliche interne DSL resultiert.

 

   1:  // Beispiel einer internen DSL mit MethodChaining
   2:  var notebook = new Notebook()
   3:       .SetManufacturerTo("Dell")
   4:       .SetNameTo("XPS Studio 13")
   5:       .SetDisplaySizeTo(13.3)
   6:       .SetProcessorCountTo(4)
   7:       .SetProcessorNameTo("Intel Core i5")
   8:       .SetRamTo(4096)
   9:       .SetHarddiskTo(256, DiskType.SSD);
  10:   
  11:  Console.WriteLine(notebook.ToString());

 

Eine eigene interne DSL in C# schreiben

Eine interne DSL kann in C# recht einfach erstellt werden, hierbei empfiehlt sich allerdings die Realisierung nach TDD. Durch den Test-First Ansatz kann von Beginn an gewährleistet werden, dass die API wie gewünscht implementiert wird.

Damit der erste Test überhaupt etwas Sinn macht, sollte man allerdings erst die Typen definierten mit denen man Arbeiten möchte. In diesem Beispiel wären das Notebook und die Enumeration DiskType.

   1:  // Notebook.cs
   2:  using System;
   3:   
   4:  namespace Internal.DSL.Example
   5:  {
   6:    public class Notebook
   7:    {
   8:   
   9:    }
  10:  }
  11:   
  12:  // DiskType.cs
  13:  using System;
  14:   
  15:  namespace Internal.DSL.Example
  16:  {
  17:    public enum DiskType
  18:    {
  19:      HDD,
  20:      SSD
  21:    }
  22:  }

 

Nun der Test…

Als Testframework habe ich hier MSTest verwendet. MSTest ist ab Visual Studio 2008 Pro verfügbar. Alternativ können UnitTest Frameworks wie NUnit verwendet werden…

   1:  [TestMethod()]
   2:  public void Test_NotebookAPI()
   3:  {
   4:      // arrange
   5:      String manufacturer = "Dell";
   6:      String name = "XPS Studio 13";
   7:      String cpuName = "Intel Core i5";
   8:      int cpuCount = 4;
   9:      double displaySize = 13.3;
  10:      double ram = 4096;
  11:      double hddSize = 256;
  12:      DiskType diskType = DiskType.SSD;
  13:      
  14:      // act
  15:      Notebook notebook = new Notebook()
  16:          .SetManufacturerTo(manufacturer)
  17:          .SetNameTo(name)
  18:          .SetDisplaySizeTo(displaySize)
  19:          .SetProcessorCountTo(cpuCount)
  20:          .SetProcessorNameTo(cpuName)
  21:          .SetRamTo(ram)
  22:          .SetHarddiskTo(hddSize, diskType);
  23:   
  24:      // assert
  25:      Assert.IsNotNull(notebook);
  26:      Assert.AreEqual<String>(manufacturer, notebook.Manufacturer);
  27:      Assert.AreEqual<String>(name, notebook.Name);
  28:      Assert.AreEqual<double>(displaySize, notebook.DisplaySize);
  29:      Assert.AreEqual<int>(cpuCount, notebook.ProcessorCount);
  30:      Assert.AreEqual<string>(cpuName, notebook.ProcessorName);
  31:      Assert.AreEqual<double>(ram, notebook.Ram);
  32:      Assert.AreEqual<double>(hddSize, notebook.HddSize);
  33:      Assert.AreEqual<DiskType>(diskType, notebook.DiskType);
  34:  }
 
Gemäß dem TDD Vorgehensmodell, wird nun genau soviel Code geschrieben, wie notwendig ist, bis der Test grün ist. Also auf zur Implementierung unserer API.

Die Implementierung

Die Implementierung einer internen DSL mit MethodChaining ist in C# sehr einfach, man nutzt am einfachsten eine Kombination aus ReadOnly Property und einer Setter Methode, welche immer die aktuelle Objektinstanz zurück liefert, damit direkt im Anschluss eine weitere Methode aufgerufen werden kann.

An dieser Stelle möchte ich das ganze exemplarisch an den Methoden für den Hersteller und den Modellnamen des Notebooks zeigen.

   1:  public class Notebook
   2:  {
   3:      private string manufacturer;
   4:      private string name;
   5:      
   6:      public String Manufacturer
   7:      {
   8:          get { return manufacturer; }
   9:      }
  10:      
  11:      public String Name
  12:      {
  13:          get { return name; }
  14:      }
  15:   
  16:      public Notebook SetManufacturerTo(String aManufacturerName)
  17:      {
  18:          this.manufacturer = aManufacturerName;
  19:          return this;
  20:      }
  21:   
  22:      public Notebook SetNameTo(String aName)
  23:      {
  24:          this.name = aName;
  25:          return this;
  26:      }
  27:  }

 

Wichtig ist, dass keine Property einen Setter hat, dar sonst Entwickler die diese API verwenden eventuell in die Versuchung kommen diese zu verwenden. Dann wäre die gesamte API hinfällig. Des Weiteren sieht man an den beiden Methoden SetManufacturerTo und SetNameTo dass der Rückgabewert immer vom Typ Notebook ist.

Analog zu diesen beiden Beispielmethoden und Beispielproperties müssen nun noch die fehlenden fünf Paare entwickelt werden. Wenn alles korrekt ist, sollte auch der eben geschriebene Test grün durchlaufen.

Das gesamte Beispiel findet ihr am Ende dieses Artikels zum Download.

 

Fazit

Danke MethodChaining lassen sich schnell, attraktive APIs entwickeln. Da in einfachen Szenarien keine Generics oder sonstiges verwendet wird kann man solche APIs sogar schon in .NET 1.0 Anwendungen realisieren. Wenn man allerdings das .NET Framework ab Version 3.0 einsetzen kann eröffnen sich neue Wege, hierbei kann man auf eigenen ListTypen eine API bereitstellen, die der von IQueryable nahe kommt. Auch eine solche Abfrage-API ist sehr einfach und schnell zu lösen.

Durch die strikte Limitierung der Objekte, wird der Code auch sicherer als wenn überall automated Properties angeboten werden. Der Entwickler bekommt eine klare Verwendungsvorgabe und weiß was er zu implementieren hat.

 

Download Beispielcode

Internal.DSL.Example.zip downloaden

 

DotNetKicks-DE Image
Published Mittwoch, 7. April 2010 22:35 von ThorstenHans
Abgelegt unter: ,

Kommentare

# Interne DSL in C#

Mittwoch, 7. April 2010 22:36 von dotnet-kicks.de

Sie wurden gekickt (eine gute Sache) - Trackback von dotnet-kicks.de

# Twitter Trackbacks for Interne DSL in C# - .NET rocks [dotnet-forum.de] on Topsy.com

Ping Antwort von  Twitter Trackbacks for                 Interne DSL in C# - .NET rocks         [dotnet-forum.de]        on Topsy.com

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)