Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 16 Min.

Agil Code generieren

Ob CodeDOM, T4 oder Excel: Praxisbeispiele zeigen, wie Sie damit Code erzeugen.
© dotnetpro
Warum Code selbst eintippen, wenn der Computer Code wesentlich schneller erzeugen kann? Viele Softwareentwickler setzen daher Codegenerierung ein oder haben zumindest schon einmal über den Einsatz nachgedacht. Der erste Teil dieser Artikelserie [1] bot eine Entscheidungshilfe an, indem er Vor- und Nachteile der Codegenerierung und ihren Platz in der modellgetriebenen Softwarearchitektur (MDA) diskutiert hat. Ferner wurden dort einige empfohlene Vorgehensweisen (Best Practices) und Anwendungsfälle erklärt.Kurz zusammengefasst ergibt sich das folgende Bild: Codegenerierung bietet viele Vorteile für Entwickler wie höhere Produktivität und Agilität sowie bessere Qualität, Stabilität und Einheitlichkeit des generierten Codes. Die Codegenerierung beseitigt auch einige Nachteile von anderen Methoden für repetitiven oder generischen Code – anstelle von Copy-and-paste oder generischen Klassen beziehungsweise Bibliotheken. Das mündet in die folgende Erkenntnis: Wo es sinnvoll (siehe Abschnitt „Code Generation: Wann anwenden?“ in [1]) und vom Projektbudget her vertretbar ist, sollte man so oft und so ­aktiv wie möglich die modellgetriebenen Ansätze mit der Code­generierung anwenden.Dieser Artikel will anhand von praktischen Beispielen Wege zeigen, wie Sie Quellcode mit .NET und Visual Studio generieren. Der ultimative Fall kommt sogar ganz ohne .NET Framework aus – SQL/C#-Quellcode wird nur mithilfe einer Excel-Datei erzeugt.Alle Demoprojekte finden Sie im GitHub-Repository [2]. Von dort können Sie sie klonen beziehungsweise herunterladen und gegebenenfalls als Vorlagen für eigene Codegenerierungslösungen verwenden. Weitere ausführliche Informationen zur Codegenerierung in .NET und Visual Studio finden Sie in zahlreichen Online- und gedruckten Medien – zum Beispiel ist [3] ein guter Startpunkt dafür.

Prozess der Modellierung und Codegenerierung

Bevor wir uns ab dem nächsten Abschnitt mit konkreten Anwendungsfällen beschäftigen, erläutert der Autor noch das Paradigma (Bild 1) und die daraus resultierenden Modellierungs- und Codegenerierungsprozesse (Bild 2). Das macht die Vorgehensweisen in den entsprechenden Lösungen mit Codegenerierung klarer und verständlicher. Ein Modellierungsprozess sieht in der Regel folgendermaßen aus:
Paradigmavon modellgetriebener Softwareentwicklung(Bild 1) © Autor
Prozessder Codegenerierung(Bild 2) © Autor
  • Geschäftsdomäne definieren: Sie analysieren das zu lösende Geschäftsproblem beziehungsweise die Aufgabe und ordnen es beziehungsweise sie der entsprechenden Geschäftsdomäne zu. Für eine Buchhaltungslösung gilt das Rechnungswesen und für ein Computerspiel wäre die Gaming-Unterhaltung als Geschäftsdomäne denkbar.
  • Metamodell erstellen/auswählen: Nachdem Sie die Geschäftsdomäne gewählt haben, können Sie ein Metamodell erstellen beziehungsweise ein vorhandenes anpassen, falls kein passendes Metamodell für das zu lösende Problem in der gewählten Geschäftsdomäne vorhanden sein sollte.
  • Geschäftsmodell erstellen: Im Rahmen des verwendeten Metamodells und auf der fachlichen Abstraktionsebene der relevanten Domäne werden benötigte Werte, Beziehungen und andere Fakten spezifiziert, die eine Lösung des aktuellen fachlichen Problems formal beschreiben.
Nach der Erstellung des Geschäftsmodells folgt der Codegenerierungsprozess. Hier führen Sie einen Codegenerator aus, der die Transformation des aktuellen Modells (gegebenenfalls mit Einbeziehung entsprechender Vorlagen/Templates) in ein oder mehrere Codeartefakte durchführt.Wichtige Bestandteile einer modellgetriebenen Software-Lösung mit Codegenerierung sind:
  • Modell (Geschäftsmodell): Der wichtigste und zentrale Teil, in dem alle Fakten eines Anwendungsfalls auf einer hohen Abstraktionsebene definiert sind: die Geschäftsobjekte und gegebenenfalls deren Beziehungen und Interaktionen.
  • Metamodell: Beschreibt den Definitionsraum eines Geschäftsmodells (siehe [4] und den Kasten Metamodell).
  • Codegenerator: Ein wichtiger zentraler Baustein, sozusagen ein Motor der modellgetriebenen Softwareentwicklung. Stellt ein Programm oder ein Werkzeug dar, das die Transformationen von einer höher abstrahierten Ebene in Form eines Geschäftsmodells beziehungsweise DSL-Codes in entsprechenden spezifischen Code (mit niedriger Abstraktion) beziehungsweise Modell durchführt.
  • Vorlage (englisch: Template): Ein statischer Modell-agnostischer (unabhängiger) Teil des zu generierenden Codes. Es ist ein Skelett des zu generierenden Codes mit Platzhaltern für die Geschäftsmodelldaten.
  • Generat: Spezifische wohlformalisierte Artefakte, die das Ziel der Codegenerierung darstellen: Programmcode, spezifische Modelle, Konfigurationen, Skripte et cetera.
Das oben und in [1] beschriebene Muster modellgetriebener Softwareentwicklung gibt einen Rahmen für den Codegenerierungsprozess vor, der in Bild 2 schematisch dargestellt ist und wie folgt ablaufen kann:
  • Eingabe einlesen: Zuerst liest man spezifische Daten aus einem Modell (Geschäftsmodell) ein. Ein Modell kann dabei in verschiedenen Modalitäten vorhanden sein: in textuellem und/oder grafischem beziehungsweise tabellarischem Format. Man kann sogar ein Geschäftsmodell innerhalb ­einer öffentlichen, statischen OO-Klasse definieren.
  • Modellgraphen aufbauen: Basierend auf dem zugrunde liegenden Metamodell baut man im Programmspeicher einen Modellgraphen auf und füllt ihn mit den im ersten Prozessschritt eingelesenen Geschäftsmodelldaten. Ein Modellgraph kann ebenso wie ein Geschäftsmodell verschiedene Formen annehmen – zum Beispiel Document Object Model, kurz DOM [5], Abstract Syntax Tree, kurz AST [6], beziehungsweise eine andere dem jeweiligen Metamodell entsprechende Datenstruktur.
  • Transformation durchführen: Ein in einen Modellgraphen geladenes Geschäftsmodell wird durch den Codegenerator in das/die gewünschten Ausgabeformate wie zum Beispiel C#, Java, Python, XML, HTML, JSON und so weiter umgewandelt. Es können dabei gegebenenfalls eine oder mehrere Vorlagen (Templates) verwendet werden.
  • Ausgabe persistieren: Der im letzten Schritt generierte Code wird in einer Datei gespeichert beziehungsweise in einen anderen Datenstrom (wie zum Beispiel einen Cloud-Speicher) kopiert.
Weitere Einzelheiten finden Sie in [3] und [1] im Abschnitt „Code Generation in modellgetriebener Architektur“.Nun sind Sie mit dem nötigen theoretischen Grundwissen für die erste Schritte in der Codegenerierung ausgestattet und es kann endlich mit der Praxis losgehen.

Definition von Meta- und Geschäftsmodellen

Um den Lesern das Verständnis des gesamten Modellierungs- sowie Codegenerierungsprozesses zu erleichtern, werden wir nun eine wohlbekannte Domäne mit einem einfach zu lösenden Problem auswählen und dort ein überschaubares Metamodell aufbauen.In unserem Beispiel wird eine planare (flache) 2D-Geometrie die Rolle der Geschäftsdomäne spielen und als eine Aufgabe (Geschäftsproblem) werden wir die Berechnung der Fläche und des Umfangs einer planaren geometrischen Figur wie Kreis, Rechteck und so weiter festlegen. In diesem Fall kann wohl jeder ein Geschäftsexperte sein!In der Tabelle 1 ist ein konzeptionelles Metamodell dargestellt, das notwendige Konzepte beschreibt, die es erlauben, Geschäftsmodelle zu entwerfen, um die Berechnung von Fläche (Area) und Umfang (Perimeter) sowie einigen anderen Eigenschaften eines Polygons (2D-Figur) formal zu beschreiben. In Tabelle 2, Tabelle 3 und Tabelle 4 (Tabelle 4 finden Sie aus Platzgründen nur in den Downloads zum Artikel) werden wiederum entsprechende Polygonmodelle jeweils für Kreis-, Rechteck- und Trapez-Figuren spezifiziert.

Tabelle 2: Polygon-Modell - Kreis

Nr. Konzept-­Bezeichnung Beschreibung Regel Beispiele
1 Polygon Name des Polygons. Eine zweidimensionale (flache) geometrische Figur. Rechteck, Dreieck, Kreis etc.
2 Property (Eigenschaft) Name/Bezeichnung/Wert der Polygon-Eigenschaft(en). Eines oder mehrere Merkmale, die das Polygon vollständig definieren. Radius r (Kreis), Seitelänge a (Quadrat), Seitenlängen a, b (Rechteck etc.)
3 Area (Fläche) Umfasst alle Punkte, die sich im Inneren oder auf dem Rand der Figur befinden. Wird durch die Formel mit relevanten Eigenschaften eindeutig berechnet (siehe Beispiele). A = π · r2 (Kreis) A = a · a = a2 (Quadrat), A = a · b (Rechteck)
4 Perimeter (Umfang) Umfasst alle Punkte, die sich auf dem Rand der Figur befinden. Wird durch die Formel mit relevanten Eigenschaften eindeutig berechnet (siehe Beispiele). U = 2 · π · r (Kreis), U = 4 · a (Quadrat), U = 2 · a + 2 · b (Rechteck)
5 Co-Property (Co-Eigenschaft) Name/Bezeichnung/Formel einer abgeleiteten Polygon-Eigenschaft. Das Polygon kann weitere Eigenschaften haben, die von Haupteigenschaften abgeleitet (berechnet) werden können. Durchmesser d = 2 · r (Kreis), Diagonale d = a · √2 (Quadrat)
6 π (Pi) Mathematische Konstante. Eine mathematische Konstante, die als Verhältnis des Umfangs eines Kreises zu seinem Durchmesser definiert ist. 3,14159265359
Wie man in den Polygonmodellen sieht, sind dort nicht nur abstrakte (mathematische), sondern auch sprachspezifische (C#/.NET) formelle Ausdrücke für relevante Eigenschaften definiert. Ferner sind konkrete jeweilige Dateneingaben gemacht (zum Beispiel Radiuswert für den Kreis oder Breite und Höhe für das Rechteck und so weiter), mit denen die Ziel­eigenschaften (Fläche/Umfang) und gegebenenfalls die Co-Eigenschaften berechnet werden können. Das wird unmittelbar bei der Umsetzung der Codegenerierung behilflich sein.Nun haben wir die Geometrie als Geschäftsdomäne der zu lösenden Aufgabe der Flächen- und Umfangsberechnung sowie ein dazu passendes Metamodell mit entsprechenden konkreten Polygonmodellen für Kreis, Rechteck und Trapez definiert. Damit gilt der Modellierungsprozess als abgeschlossen und es kann mit der Codegenerierung weitergehen.

Die Demoprojektmappe

Auf der GitHub-Seite unter [2] finden Sie die Projektmappe Agile­CodeGeneration mit Demoprojekten, die in den nachfolgenden Abschnitten referenziert werden. Um die Demoprojekte lokal zu beziehen, öffnen Sie den Link [2], klicken dann auf die grüne Schaltfläche Code und klonen das Git-­Repository beziehungsweise laden es als ZIP-Datei herunter. Entpacken Sie es und öffnen Sie schließlich in Visual Studio 2019/2022 die Projektmappe AgileCodeGeneration.sln. Dort finden Sie in den folgenden Verzeichnisse die Artefakte vor:
  • 1. Models | GeometricModels: Eine Bibliothek mit den Kreis- und Trapez-Modellen (als statische öffentliche C#-Klassen).
  • 1. Models | GeometricModelsCore: Eine Bibliothek mit dem Rechteck-Modell (als statische öffentliche C#-Klasse).
  • 2. Code Generation - Direct | GenExampleDirect: Eine per Hand umgesetzte Codegenerierung.
  • 3. Code Generation - CodeDOM | GenExampleCodeDOM: Codegenerierungsprojekt mit CodeDOM-Technologie.
  • 3. Code Generation - CodeDOM | TargetCodeDOM: Zielprojekt für CodeDOM-Codegenerierung.
  • 4. Code Generation - T4 | 4.1_static | GenExampleT4: Codegenerierungsprojekt mit T4-Textvorlage.
  • 4. Code Generation - T4 | 4.2_dynamic | GenExampleT4Dynamic: Codegenerierungsprojekt mit T4-Laufzeit-Textvorlagen.
  • 4. Code Generation - T4 | 4.2_dynamic | TargetRuntimeT4: Zielprojekt für Codegenerierung mit T4-Laufzeit-Textvorlagen.
  • 5. Code Generation - Excel | ExcelSqlCodeGen.xlsx: Excel-Codegenerierungsmodell für SQL-Skripte.
  • 5. Code Generation - Excel | PolygonCodeGenerator.xlsm: Excel-Codegenerierungsmodell für C#-Klassen.
  • 5. Code Generation - Excel | PolygonTestCircle/-Rectangle/-Trapezoid: C#-Zielprojekte für die Codegenerierung mit Excel-Modell.
Einige der C#-Projekte haben .NET Core 3.1, einige andere .NET 4.7.2 als Zielframework, um zu demonstrieren, dass die Codegenerierung mit .NET auch auf Multiplattform-Basis laufen kann.In den nächsten Abschnitten finden Sie die Beschreibung zu den jeweiligen Beispielprojekten. Alle Demoprojekte folgen immer dem gleichen Codegenerierungsprozess, der bereits im vorletzten Abschnitt beschrieben wurde: Zuerst ein Modell einlesen, dann einen Modellgraphen aufbauen, anschließend eine Transformation durchführen und schließlich das Ergebnis speichern.Ein Geschäftsmodell kann wie erwähnt in verschiedenen Formen und Formaten definiert sein – grafisch oder textuell, als eine XML-JSON- beziehungsweise CSV-Datei, oder es kann sich in einer Datenbank befinden. Dieser Artikel soll sich auf die Codegenerierungsaspekte fokussieren. Darum wird meistens ein Geschäftsmodell in Form einer statischen öffentlichen C#-Klasse verwendet, wie es zum Beispiel gleich im folgenden Abschnitt passiert. Dabei erledigt man zwei Schritte des Codegenerierungsprozesses auf einen Streich: Modell einlesen und den Modellgraphen aufbauen, da eine öffentliche statische C#-Klasse sowohl zur Entwurfs- als auch zur Laufzeit sofort zur Verfügung steht.

Codegenerierung per Hand

Die .NET-Plattform und Visual Studio besitzen einige Codegenerierungstechnologien, die in den Beispielprojekten in den nächsten Abschnitten angewandt werden. Dennoch werden wir, um die Grundprinzipien von Codegenerierung verständlicher zu machen, eine Codegenerierungslösung zuerst komplett per Hand ohne jegliche interne oder externe Tools entwickeln.Zuerst definiert man ein Kreismodell in der statischen, ­öffentlichen Klasse CircleModel(Listing 1). Dann führt die Methode GenerateHtmlReport() die Transformation des Kreismodells in eine HTML-Seite aus (Listing 2) und liefert einen entsprechenden String-Ausdruck zurück, der in der Datei gespeichert und dann im Browser dargestellt wird.
Listing 1: Kreis-Modell als statische C#-Klasse
namespace PolygonModels {
  public static class CircleModel {
    // definitions
    public const string Namespace = "PolygonSamples";
    public const string Class = "SampleCircle";
    public const string Types = "double,double";
    public const string Fields = "radius,diameter";
    public const string Properties =  
      "Radius,Diameter";
    public const string Values = "10,20";
    // calculations
    public static string[] PropertiesValues = 
      Values.Split(',');
    public static string[] PropertiesNames = 
      Properties.Split(',');
    public static double RadiusValue = 
      Convert.ToDouble(PropertiesValues[0]);
    public static double DiameterValue = 
      2 * RadiusValue;
    public static double AreaValue = 
      Math.PI * Math.Pow(RadiusValue, 2);
    public static double PerimeterValue = 
      Math.PI * DiameterValue;
  }
} 
Listing 2: Transformation des Kreis-Modells als HTML-Seite
static void Main(string[] args) {
  // init
  string gencodeReportText = "";
  string fileHtmlReport = "PolygonReport.html";
  // generate polygon report (HTML-code)
  gencodeReportText = GenerateHtmlReport();
  // write report to the file and show in browser
  System.IO.File.WriteAllText(
    fileHtmlReport, gencodeReportText);
  System.Diagnostics.Process.Start(fileHtmlReport);
}

private static string GenerateHtmlReport() {
  string reportResult = "";
  reportResult += "<!--Dieser Code wurde von 
    einem Tool generiert. -->";
  reportResult += $"<h1> Report of 
    \"{CircleModel.Class}\"-Object </h1>";
  reportResult += "    <hr><h2> Properties:</h2>";
  reportResult += "        <table>";
  for (int i = 0; 
      i < CircleModel.PropertiesValues.Length; i++)
  {
    reportResult += "        <tr>";
    reportResult += $"        <td> 
      {CircleModel.PropertiesNames[i]} = </td>";
    reportResult += $"        <td> 
      {CircleModel.PropertiesValues[i]} </td>";
    reportResult += "        </tr>";
  }
  reportResult += "</table>";
  reportResult += "<hr><h2> Calculated Values:</h2>";
  reportResult += $"    
    Area = {CircleModel.AreaValue},";
  reportResult += $"    
    Perimeter = {CircleModel.PerimeterValue}";
  reportResult += "</body></html>";
  return reportResult;
} 
Beachten Sie die Zeichenfolgeninterpolation [7] (seit C# 6.0 verfügbar) bei der Texttransformation (Listing 2). Wenn eine Zeichenfolge vom Typ String mit einem $-Zeichen beginnt, kann sie die auszuwertenden Ausdrücke mit geschweiften Klammern einschließen, ohne die String.Format()-Funktion aufrufen zu müssen, zum Beispiel:
reportResult += $"

Report of \"   {CircleModel.Class}\"-Object

";
Die für diesen Abschnitt relevanten Demo-Projekte sind GeometricModels und GenExampleDirect.

Sprachneutrale Codegenerierung mit .NET

Wenn man den Code in verschiedenen Programmiersprachen generieren möchte, kann dabei der im .NET Framework enthaltene Mechanismus namens Code Document Object Model (kurz CodeDOM) [8] helfen. CodeDOM baut eine Beschreibung in einem CodeCompileUnit-Objekt mit einer Sammlung der verknüpften Objekte auf, die Elemente des generierenden Codes repräsentieren – Namensräume, Klassen, Eigenschaften, Methoden et cetera. Solche verknüpften Objekte bilden eine Datenstruktur, die als CodeDOM-Diagramm bekannt ist und die Zielstruktur des zu generierenden Codes modelliert. Ein CodeDOM-Diagramm wird dann an den ­jeweiligen Codeprovider gebunden, der den Code für die ­gewählte Zielsprache generiert. Im .NET Framework stehen standardmäßig die Codeprovider für C#, JavaScript und ­Visual Basic zur Verfügung. Die Erstellung eines jeweiligen Providers kann wie folgt aussehen:
var csProvider = new CSharpCodeProvider();
var csProvider2 = 
  CodeDomProvider.CreateProvider ("CSharp");
var jsProvider = 
  CodeDomProvider.CreateProvider ("JScript");
var vbProvider == CodeDomProvider.CreateProvider ("VB"); 
Die Liste von Codeprovidern kann auch von Entwicklern selbst erweitert werden, um die Unterstützung für weitere Programmiersprachen einzuführen.Im Namensraum System.CodeDom sind die Typen für die Programmiersprachen-neutralen Darstellungen der logischen Struktur des Quellcodes definiert, und im ­Namensraum System.CodeDom.Compiler wiederum sind die Typen enthalten, die zum Generieren von Quellcode von CodeDOM-Diagrammen und zum Verwalten der Quellcodekompilierung in unterstützten Sprachen verwendet werden können.Wenn man das Konsolenprogramm GenExampleCodeDOM ausführt, wird die Methode GenerateCodeInTargetProject() aufgerufen und die Datei Main.cs im Projekt TargetCodeDOM persistiert. Sie können dieses Projekt ausführen, um festzustellen, dass die Codegenerierung korrekt gelaufen ist. In Listing 3 sind der Konstruktor und die AddFields()-Methode der Codegenerierungsklasse CodeDOMGeneratorSample dargestellt. In den Listing 4 und Listing 5 kann man die Codegenerierungsergebnisse für die C#-und VB-Variante sehen. Dabei handelt es sich um Auszüge – den vollständigen Code finden Sie im Projekt GenExampleCodeDOM.
Listing 3: CodeDOM, Codegenerierungsklasse (Auszug)
// Define the class.
public CodeDOMGeneratorSample() {
  targetUnit = new CodeCompileUnit();
  CodeNamespace samples = 
    new CodeNamespace("CodeDOMPolygonSample");
  samples.Imports.Add(
    new CodeNamespaceImport("System"));
  targetClass = 
    new CodeTypeDeclaration("CodeDOMRectangleClass");
  targetClass.IsClass = true;
  targetClass.TypeAttributes = 
    TypeAttributes.Public | TypeAttributes.Sealed;
  samples.Types.Add(targetClass);
  targetUnit.Namespaces.Add(samples);
}

// Adds two fields to the class.
public void AddFields() {
  // Declare the widthValue field.
  CodeMemberField widthValueField = 
    new CodeMemberField();
  widthValueField.Attributes = 
    MemberAttributes.Private;
  widthValueField.Name = "widthValue";
  widthValueField.Type =  
    new CodeTypeReference(typeof(System.Double));
  widthValueField.Comments.Add(
    new CodeCommentStatement(
      "The width of the object."));
  targetClass.Members.Add(widthValueField);

  // Declare the heightValue field
  CodeMemberField heightValueField = 
    new CodeMemberField();
  heightValueField.Attributes = 
    MemberAttributes.Private;
  heightValueField.Name = "heightValue";
  heightValueField.Type = new CodeTypeReference(
    typeof(System.Double));
  heightValueField.Comments.Add(
    new CodeCommentStatement(
      "The height of the object."));
  targetClass.Members.Add(heightValueField);
} 
Listing 5: Generierte VB-Klasse
Namespace CodeDOMPolygonSample
  Public NotInheritable Class CodeDOMRectangleClass
    'The width of the object
    Private widthValue As Double
    'The height of the object
    Private heightValue As Double
  End Class
End Namespace 
Listing 4: Generierte C#-Klasse
namespace CodeDOMPolygonSample {
  using System;
  public sealed class CodeDOMRectangleClass {
    // The width of the object
    private double widthValue;
    // The height of the object
    private double heightValue;
  }
} 
Trotz unbestrittener Vorteile wie zum Beispiel der Sprach­unabhängigkeit weist die CodeDOM-Technologie auch gewisse Nachteile auf. Der allergrößte Nachteil wird offensichtlich, wenn man Listing 3 mit Listing 4 beziehungsweise Listing 5 vergleicht. Um ein Dutzend Codezeilen zu generieren, muss man dreimal so viel Generierungscode schreiben. Das ist alles andere als produktiv. Darum sollte man sich für CodeDOM nur entscheiden, wenn Sprachunabhängigkeit unbedingt gefordert und budgetiert ist. Zu den weiteren Nachteilen zählen die limitierte Unterstützung von Sprachkonstrukten sowie die fehlende Unterstützung von weiteren Sprachen wie zum Beispiel Python oder SQL.Die für dieses Abschnitt relevanten Demoprojekte sind Gen­ExampleCodeDOM und TargetCodeDOM.

Vorlagenbasierte Codegenerierung mit .NET

Es gibt jedoch in .NET eine Technologie, die die Komplexität und den Entwicklungsaufwand einer Codegenerierungslösung deutlich reduziert: das Text Template Transformation Toolkit oder kurz T4 [9].Anstatt ein Programm zu schreiben, welches das andere Programm komplett implementiert, nutzt man hier eine Textvorlage, die fast wie die auszugebende Datei aussieht. Nur an den variablen (vom Geschäftsmodell abhängigen) Stellen wird Generierungscode injiziert. In einer T4-Vorlage öffnet ein <#-Zeichen einen Codegenerierungsblock. Mit #> schließt man ihn wieder. Der übrige Text wird eins zu eins in die Ausgabedatei geschrieben. Der vorliegende Artikel wird nicht die Einzelheiten der T4-Technologie erläutern, möchte aber auf einen möglichen Einstiegspunkt [10] hinweisen, der beim Schreiben von T4-Textvorlagen behilflich sein kann.T4-Codegenerierung findet auch in .NET und Visual Studio rege Verwendung – unter anderem im .NET Entity Framework [11]. Es gibt zwei Arten von T4-Textvorlagen, die zur Entwurfszeit (Designtime) [12] oder zur Laufzeit (Runtime) [13] ausgeführt werden. In einem Visual-Studio-Projekt legt das die Eigenschaft Benutzerdefiniertes Tool fest.
  • TextTemplatingFileGenerator: zur Entwurfszeit
  • TextTemplatingFilePreprocessor: zur Laufzeit
In den folgenden Abschnitten werden diese beiden Anwendungsfälle mithilfe von Beispielprojekten demonstriert.

Generierung mit T4-Vorlagen zur Entwurfszeit

Zuerst werden wir eine Lösung (siehe GenExampleT4-Projekt) mit Entwurfszeit-Textvorlagen [12] entwickeln. Dafür fügt man zu einem Projekt ein neues Element hinzu, zum Beispiel Main.tt. Alle T4-Textvorlagendateien haben immer die Erweiterung .tt. Die neu erstellte Textvorlage referenziert ein Rechteck-Modell (Listing 6). Dafür wird die T4-Direktive assembly verwendet:
Listing 6: Rechteck-Modell (statische C#-Klasse)
namespace FlatModels {
  public static class RectangleModel {
    public const string Namespace = 
      "PolygonSamples";
    public const string Class = "SampleRectangle";
    public const string Types = "double,double";
    public const string Fields = "width,height";
    public const string Properties = "Width,Height";
    public const string InitialValues =  
      "15.0D,10.0D";
    public const string AreaFormula = 
      "Width * Height";
  }
} 
<#@ assembly name="$(SolutionDir)GeometricModelsCore
  \bin\Debug\netcoreapp3.1\GeometricModelsCore.dll" #> 
Sonstige Umsetzungseinzelheiten kann man in der Datei Main.tt nachschlagen oder sich einen Auszug davon im Listing 7 ansehen.
Listing 7: Rechteck Textvorlage
<#@ template debug="false" hostspecific="false" 
  language="C#" #>
<#@ assembly name="$(SolutionDir)GeometricModelsCore
  \bin\Debug\netcoreapp3.1\GeometricModelsCore.dll" #>
<#@ import namespace="FlatModels" #>
<#@ output extension=".cs" #>

namespace <#= RectangleModel.Namespace #> {
  public sealed class <#= RectangleModel.Class #> {
    <#
      var types = RectangleModel.Types.Split(',');
      var props = RectangleModel.Properties
        .Split(',');
      var fields = RectangleModel.Fields.Split(',');
      var i = 0;
      foreach (var field in fields)
      {#>
        // The <#=field#> of the object
        private <#=types[i]#> <#=field#>Value;
        // The <#=props[i]#> property for the object
        public <#=types[i]#> <#=props[i]#>  
          { get { return this.<#=field#>Value; }  }
      <# // next model member
      i++; 
    }#>
  }
} 
In dem Moment, wenn Sie die Textvorlage speichern, erzeugt Visual Studio eine Ergebnisdatei und verknüpft sie mit Ihrer Vorlage im VS-Projekt. Deswegen ist es wichtig, dass das Geschäftsmodell bereits zur Entwurfszeit im Codegenerierungskontext zur Verfügung steht. Das funktioniert auch mit der statischen, öffentlichen Modellklasse Rect­angleModel aus der referenzierten Assembly GeometricModelsCore.dll wunderbar.Im Listing 8 ist eine solche Ergebnisdatei Main.cs dargestellt, die dem Auszug in Listing 7 entspricht.
Listing 8: Rechteck C#-Klasse
namespace PolygonSamples {
  using System;
  public sealed class SampleRectangle {  
    // The width of the object
    private double widthValue;
    // The height of the object
    private double heightValue;
  }
}  
Anhand der Main.tt-Textvorlage wird ein vollständiger C#-Code in der Datei Main.cs generiert, der sofort ausgeführt werden kann. Das Programm gibt dabei die Eingabeparameter – Breite und Höhe und die Fläche des im Geschäftsmodell definierten Rechtecks auf der Konsole aus. Das für dieses Abschnitt relevante Demo-Projekt ist GenExampleT4.

Generierung mit T4-Textvorlagen zur Laufzeit

Was können Sie machen, wenn die Geschäftsmodelldaten für generierte Inhalte nicht zur Entwurf-, sondern erst zur Laufzeit zu Verfügung stehen und/oder die Codegenerierungsausgabe nicht in eine Projektdatei von Visual Studio, sondern in eine zur Entwurfszeit nicht greifbare Datei beziehungsweise einen Datenstrom stattfinden soll?Dafür kann man eine andere Art der T4-Textvorlagen verwenden – die Laufzeittextvorlagen. Der Trick dabei besteht darin, dass die Laufzeittextvorlage von einem anderen Werkzeug verarbeitet wird: dem TextTemplatingFilePreprocessor. Dabei wird statt der Ergebnisdatei selbst ein Programm generiert, das ein anderes Programm beziehungsweise eine Text­ausgabe schreibt (Listing 9). Für Demozwecke haben wir noch eine weitere Laufzeittextvorlage hinzugefügt: MainReport.tt(Listing 10), die eine weitere HTML-Datei aus demselben TrapezoidModel generiert.
Listing 9: C#-Code (Auszug, generiert von Main.tt)
namespace GenExampleT4Dynamic {
  using FlatModels;
  using System;
  ...    
  public partial class Main : MainBase {
    ...
    public virtual string TransformText() {
      ...
      this.Write(this.ToStringHelper
        .ToStringWithCulture(
          TrapezoidModel.Namespace));
      this.Write("\r\n{\r\n using System; 
        \r\n    \r\n    public sealed class ");
      this.Write(this.ToStringHelper
        .ToStringWithCulture(TrapezoidModel.Class));
      this.Write("\r\n    {  ");

      var types = TrapezoidModel.Types.Split(',');
      var props = TrapezoidModel.Properties
        .Split(',');
      var fields = TrapezoidModel.Fields.Split(',');
      ...
    }
  }
} 
Der Codegenerierungs- und Testprozess mit Laufzeittextvorlagen ist in Listing 11 dargestellt:
Listing 11: Codegenerierung mit Laufzeit-Textvorlagen – Program.cs
class Program {
  public const string OutputFileName = "Main.cs";
  public const string TargetProject = 
    "TargetRuntimeT4";
  public static string OutputFileOfTargetProject {
    get { return $@"..\..\..\{TargetProject}
      \{OutputFileName}"; }
  }
  public static string ReportFilename 
    { get { return "MainReport.html"; } }

  static void Main(string[] args) {
    var gencodeMain = new Main();
    var gencodeReport = new MainReport();
    String gencodeMainText = 
      gencodeMain.TransformText();
    String gencodeReportText = 
      gencodeReport.TransformText();
    System.IO.File.WriteAllText(
      OutputFileOfTargetProject, gencodeMainText);
    System.IO.File.WriteAllText(
      ReportFilename, gencodeReportText);

    Console.WriteLine("Generated file: " + 
      OutputFileOfTargetProject);
    System.Diagnostics.Process.Start(ReportFilename);
  }
} 
  • Man instanziert zwei Codegenerierungsobjekte gencodeMain und gencodeReport, deren Klassen wiederum aus den Laufzeittextvorlagen Main.tt und MainReport.tt generiert worden sind.
  • Man ruft die jeweilige Funktion TransformText() auf und speichert die generierten Ergebnisse in den lokalen String-Variablen.
  • Die Ergebnisse werden jeweils als Main.cs in das Zielprojekt und als MainReport.html in das aktuelle Laufzeitverzeichnis geschrieben.
  • Ein Browser zeigt den generierten HTML-Bericht im Browser an.
  • Um die Korrektheit der generierten Datei Main.cs zu überprüfen, führt man das Programm vom Zielprojekt (Target­RuntimeT4) aus.
Die für dieses Abschnitt relevanten Demoprojekte sind Gen­ExampleT4Dynamic und TargetRuntimeT4.

Agile Codegenerierung mit Microsoft Excel

Stellen Sie sich vor, Sie müssen ein neues Feld zu einer Tabelle einer relationalen Datenbank hinzufügen. Eigentlich ist das keine komplizierte Aufgabe, und sie könnte durch die Ausführung eines DDL-Skripts mit folgendem Muster gelöst werden:
ALTER TABLE [].[].[]  
  ADD [] nvarchar(255) NULL 
Wie aber sieht es aus, wenn statt einer zehn, 100 oder 1000 Tabellen aktualisiert werden müssen? Anstatt viele Male die Zwischenablage zu beanspruchen, kann die Codegenerierung hier gute Dienste leisten.Man kann wie bereits beschrieben ein Modell mit den betroffenen Tabellen entwerfen und eine Codegenerierungslösung entwickeln, zum Beispiel mit CodeDOM- oder T4-Technik.Dieser Vorgang wiederum bedeutet aber einen gewissen Aufwand. Was kann man tun, wenn es schnell und trotzdem modellgetrieben gehen soll und/oder Visual Studio mit .NET beziehungsweise Programmierkenntnisse fehlen?Die Lösung für diese Anforderung lautet ganz schlicht und einfach: Wir verwenden ein Tabellenkalkulationsprogramm wie  etwa Microsoft Excel.Zum Beispiel lässt sich die oben beschriebene Aufgabe für eine SQL-Server-Datenbank wie folgt lösen:
  • Fragen Sie zuerst mithilfe der folgenden Zeilen alle nötigen Daten aus dem Informationsschema ab:
  SELECT * FROM INFORMATION_SCHEMA.TABLES
  where table_type = 'base table'
  order by table_name
  GO 
  • Fügen Sie diese Daten zu einem Modellblatt hinzu (siehe Bild 3 und Tables-Model-Blatt in ExcelSqlCodeGen.xlsx).
Tabellen-Modellvon Northwind-Datenbank(Bild 3) © Autor
  • Erstellen Sie nun ein Codegeneratorblatt und schreiben Sie dort eine Excel-Formel für den benötigten SQL-Ausdruck hinein. Sehen Sie dazu Bild 4 und das CodeGen-AddColumn-Blatt.
Generierungdes DDL-Skripts für erste Tabelle(Bild 4) © Autor
  • Im Codegeneratorblatt kopieren Sie die neuen Datensätze mit [Strg]+[C]/[Strg]+ [V] so lange, bis Sie alle Tabellen aus dem Modellblatt referenziert haben (vergleichen Sie dazu Bild 5). Dabei werden entsprechende Tabellennamen von Excel ­automatisch aktualisiert.
Generierungdes DDL-Skripts für alle Tabellen(Bild 5) © Autor
  • Im Blatt CodeGen-DeleteColumn ist analog ein Szenario umgesetzt, in dem ein Feld aus allen vorhandenen Tabellen gelöscht wird.
Mit Excel lassen sich tatsächlich alle wichtigen Bestandteile einer modellgetriebenen Softwarelösung mit Codegenerierung umsetzen: Metamodell, Geschäftsmodell, Codegenerator, Vorlage und Textausgabe.Dabei werden sowohl die Eingaben (Metamodell/Geschäftsmodell) als auch die Ausgaben (Vorlage/Textausgaben) in jeweiligen Arbeitsblättern untergebracht. Ein Codegenerator kann als Excel-Formelausdruck und/oder als VBA-Skript umgesetzt werden.In Bild 6 sehen Sie ein Beispiel (siehe PolygonCodeGenerator.xlsm), wie ein C#-Programm aus einem Rechteckmodell generiert wird. Nachfolgend sind die Ausführungsschritte dafür beschrieben:
C#-Klassengenerierungfür das Rechteck-Modell(Bild 6) © Autor
  • Man definiert ein Polygon-Metamodell (siehe Blatt Meta-Modell und Tabelle 1).
  • Im Rahmen des Polygon-Metamodells erstellen Sie ein Rechteckmodell (siehe Blatt Model-Rectangle) und setzen dort die Werte für die Breite und Höhe des Rechtecks fest.
  • Im Blatt CodeGenerator-Rectangle wählen Sie den Modellnamen (B3 in Bild 6).
  • Wählen Sie die Zellen C3:F3 aus und kopieren Sie diese mit dem VBA-Makro MacroInsertCellCopy ([Strg]+[Umschalt] +[G], siehe Listing 12) so lange, bis vom referenzierten Modell (Blatt Model-Rectangle) keine Daten mehr ankommen. Die eventuell zu viel kopierten Zellen (ohne Modelldaten) kann man gegebenenfalls auswählen und durch das Drücken von [Strg]+[Umschalt]+[D] mit MacroDeleteSelectedCells-Makrolöschen (siehe Listing 12). Als Ergebnis werden die Modelldaten in den Bereichen B3, B6, B9, B12 sowie dem Bereich C3:F4 (für Kreis- und Trapez-Modell jeweils C3:F3 und C3:F13) referenziert.
Listing 12: Zelleninhalte kopieren/löschen (VBA-Makro)
'----------------------------------------------
Sub MacroInsertCellCopy()
' Ctrl+Shift+G
'----------------------------------------------
    Selection.Copy
    Selection.Offset(1, 0).Select
    Selection.Insert Shift:=xlDown
End Sub

'----------------------------------------------
Sub MacroDeleteSelectedCells()
' Ctrl+Shift+D
'----------------------------------------------
    Application.CutCopyMode = False
    Selection.Delete Shift:=xlUp
End Sub 
  • Die einzelnen Referenzen (hellrot) in der Codegenerierungsvorlage (siehe Spalte A im Blatt CodeGenerator-Rectangle und in Bild 6) sowie die ersten Zeilen der Eingabeeigenschaften (gelb) und Ergebniseigenschaften (hellgrün) werden automatisch zugewiesen. Weitere Exemplare von Eingabe-/Ergebnis-Eigenschaften können analog wie im letzten Schritt hinzugefügt werden ([Strg]+[Umschalt]+[G]).
  • Der generierte Code (A2:A49) wird manuell oder mit dem VBA-Makro CopyGenCodeToClipboard ([Strg]+[Umschalt] +[C], siehe Listing 13) in die Zwischenablage kopiert.
Listing 13: Generierten Code in die Zwischenablage kopieren
'----------------------------------------------
Public Sub CopyGenCodeToClipboard()
' Ctrl+Shift+C
'----------------------------------------------
    ' Find the last row of code
    Dim lastCodeRow As Integer
    lastCodeRow = Cells(Rows.Count, 1).End(xlUp).Row
    ' Copy the generated code from the 
    ' current sheet to the clipboard
    Range("A2:A" & CStr(lastCodeRow)).Copy
    ' Say OK-Message
    MsgBox CStr(lastCodeRow - 1) _
        & " of the generated Code have been 
          copied to the clipboard!"
End Sub 
  • Der generierte Code wird aus der Zwischenablage in die Datei Program.cs von Zielprojekt PolygonTestRectangle übertragen.
  • Das Zielprojekt PolygonTestRectangle wird übersetzt und ausgeführt, um zu testen, dass alles erwartungsgemäß verlaufen ist.
Analoge Schritte kann man durchführen, um aus Kreis- und Trapezoid-Modellen den C#-Code zu generieren. Die für dieses Abschnitt relevanten Excel-Dateien sind ExcelSqlCodeGen.xlsx und PolygonCodeGenerator.xlsm, die Demo-Zielprojekte heißen PolygonTestCircle, PolygonTestRectangle und PolygonTestTrapezoid.

Fazit

Mit modellgetriebenen Methoden mit Codegenerierung lassen sich in der Regel höhere Produktivität und Agilität bei der Softwareentwicklung erreichen. Ferner werden dadurch die Qualität, Stabilität und die Einheitlichkeit des generierten Codes deutlich erhöht.Visual Studio und das .NET Framework bieten diverse Techniken für die Codegenerierung an wie zum Beispiel CodeDOM oder T4. Es gibt außerdem jede Menge freie und kommerzielle Werkzeuge, die Modellierung und Codegenerierung unterstützen [14] [15]. Ferner ist die Codegenerierung mit XSL-Transformation [16] oder sogar als Ad-hoc-Szenario mit Tabellenkalkulationsprogrammen wie Microsoft Excel machbar.Die Codegenerierungslösung ist meistens nicht komplexer als andere Programmierlösungen. Die Codegenerierungslösung kann entweder als fertiges Tool bezogen oder (fast) von jedem bei Bedarf entwickelt werden. Modellgetriebene Methodik mit Codegenerierung sollte man überall anwenden, wo sie Sinn ergibt und den gegebenen Projektanforderungen nicht widerspricht (zum Beispiel kein Budget für eine Codegenerierungslösung).Der Autor ermuntert die Leser dazu, mit den Demoprojekten [2] zu experimentieren.
Projektdateien herunterladen

Fussnoten

  1. Mykola Dobrochynskyy, Auf Knopfdruck Code, dotnetpro 1/2022, Seite 80 ff.,
  2. Demo-Projektmappe zum Artikel „Agil Code generieren“,
  3. Peter Vogel, Practical Code Generation in .NET, Addison-Wesley, 2010, ISBN 978-0-321-60678-5,
  4. Metamodell,
  5. Document Object Model – DOM,
  6. Abstract Syntax Tree – AST,
  7. Zeichenfolgeninterpolation (C#-Referenz),
  8. Code Document-Object-Model (CodeDOM),
  9. T4 – Text Template Transformation Toolkit,
  10. Schreiben einer T4-Vorlage,
  11. Entity Framework 6, Designer-Vorlagen für die Codegenerierung,
  12. Codegenerierung mit T4-Textvorlagen zur Entwurfszeit,
  13. Codegenerierung mit T4-Textvorlagen zur Laufzeit,
  14. Codegenerierungstools,
  15. UML-Tools,
  16. XSL Transformation,

Neueste Beiträge

Müssen Ziele SMART sein?
Wenn es um Ziele im Projektmanagement oder in der Führung einer Organisation geht, stoßen wir schnell und fast ausnahmslos auf das Akronym SMART. Was steckt dahinter, und kann es nicht auch sinnvolle Ziele geben, die nicht SMART sind?
8 Minuten
Arbeiten mit Tabellen und KI in Dataverse
Microsoft unterstützt die zentrale Datenmanagement-Lösung Dataverse in Power Apps mit KI-Features.
7 Minuten
6. Aug 2025
Browser-Apps mit Avalonia entwickeln - Avalonia
Klassische UI-Frameworks finden ihren Weg in den Browser
7 Minuten
11. Aug 2025
Miscellaneous

Das könnte Dich auch interessieren

Sicher ist sicher - Azure DevOps Pipelines Security
Als integraler Bestandteil der Entwicklungsumgebung ist Azure DevOps Pipelines oft Ziel von Angriffen. Da ist es gut zu wissen, wo die Schwachstellen des Systems liegen.
14 Minuten
16. Jun 2025
CodeProject.AI Server in neuer Version - Lokaler AI-Server
CodeProject.AI Server (jetzt in Version 2.1.10) ist ein lokal installierter, selbstgehosteter, schneller, kostenloser und Open Source Artificial Intelligence Server für jede Plattform und jede Sprache.
2 Minuten
Für Einsteiger: Backend-Webentwicklung mit .NET - Microsoft
Auf YouTube bietet Microsoft eine Videoserie für Einsteiger in die Backend-Webentwicklung mit .NET.
2 Minuten
13. Feb 2024
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige