Metaprogrammierung in C#: Wenn Code Code schreibt
Stellen Sie sich vor, Sie sind ein Architekt, der nicht nur ein Haus baut, sondern einen Roboter-Bauarbeiter, der selbstständig Wände hochzieht, Leitungen verlegt und Fassaden streicht. In der Welt von C# ist dieser „Roboter“ die Metaprogrammierung – die Kunst, Programme zu schreiben, die andere Programme (oder sich selbst) zur Laufzeit erstellen, analysieren oder modifizieren.
Wozu braucht man das? Stellen Sie sich vor, Sie schreiben Dutzende gleichartiger Klassen für die Arbeit mit JSON, erstellen komplexe Plugin-Systeme oder haben keine Lust, hunderte Methoden manuell per Reflection aufzurufen. Metaprogrammierung in C# ermöglicht es, Routineaufgaben zu automatisieren, flexible Frameworks zu erstellen und die Entwicklung um ein Vielfaches zu beschleunigen. Heute befassen wir uns mit den drei Säulen dieser Technik: Reflection, Expression Trees und dynamischer Codegenerierung.
Dieser Artikel richtet sich an alle, die bereits sicher in C# programmieren, aber einen Blick unter die Haube der CLR werfen und die Magie erlernen möchten, die einen Senior von einem Mid-Level-Entwickler unterscheidet. Los geht's!
Reflection: Röntgenblick für Ihren Code
Reflection ist das Fundament der Metaprogrammierung in C#. Sie ermöglicht es, Informationen über Typen, Methoden, Eigenschaften und Felder zur Laufzeit zu erhalten und diese dynamisch aufzurufen. Stellen Sie sich vor, Sie betrachten ein Objekt wie ein offenes Buch und können jede Seite darin lesen.
Informationen über einen Typ abrufen
Der einfachste Weg ist die Verwendung des Schlüsselworts typeof oder der Methode GetType():
Type stringType = typeof(string);Console.WriteLine($"Name: {stringType.Name}"); // StringConsole.WriteLine($"Namespace: {stringType.Namespace}"); // SystemConsole.WriteLine("Ist es eine Klasse? " + stringType.IsClass); // TrueDynamischer Methodenaufruf
Angenommen, Sie haben eine Klasse Calculator und möchten deren Methode Add aufrufen, deren Name erst zur Laufzeit bekannt ist (z. B. aus einer Konfiguration):
public class Calculator{ public int Add(int a, int b) => a + b;}
// Reflection verwendenCalculator calc = new Calculator();Type calcType = typeof(Calculator);MethodInfo? addMethod = calcType.GetMethod("Add");
if (addMethod != null){ int result = (int)addMethod.Invoke(calc, new object[] { 5, 3 }); Console.WriteLine($"Ergebnis: {result}"); // 8}Wann ist Reflection nützlich?
- Serialisierung / Deserialisierung – Bibliotheken wie Newtonsoft.Json nutzen Reflection intensiv.
- Plugins – Laden externer Assemblys und Aufruf ihrer Methoden.
- Testen – Zugriff auf private Felder und Methoden (auch wenn man es besser vermeiden sollte).
Wichtig! Reflection ist eine langsame Operation. Wenn Sie dieselbe Methode tausende Male aufrufen, denken Sie über das Caching von MethodInfo oder die Verwendung von Delegaten nach.
Expression Trees: Code als Daten
Expression Trees sind eine Möglichkeit, Code als Datenstruktur (einen Baum) darzustellen. Sie können diesen Code analysieren, modifizieren und sogar „on the fly“ kompilieren. Dies ist ein mächtiges Werkzeug zur Erstellung von ORMs (Entity Framework), Validatoren und dynamischen Abfragen.
Erstellen eines einfachen Expression Trees
Anstatt einen Lambda-Ausdruck x => x * 2 zu schreiben, bauen wir ihn manuell auf:
using System.Linq.Expressions;
// Parameter x erstellenParameterExpression param = Expression.Parameter(typeof(int), "x");// Konstante 2 erstellenConstantExpression constant = Expression.Constant(2);// Multiplikation x * 2 erstellenBinaryExpression body = Expression.Multiply(param, constant);// Alles zu einem Lambda zusammenbauen: x => x * 2Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(body, param);
// Kompilieren und ausführenFunc<int, int> compiled