1. Einführung
C# ist eine Vertreterin streng typisierter Sprachen. Das heißt, dass eine Variable zur Kompilierzeit einen definierten Typ haben muss und auf Mitglieder einer Klasse nur entsprechend diesem Typ zugegriffen werden darf. Das ist sicher, praktisch und verständlich für IDE und Compiler.
Aber manchmal muss man auf Objekte zugreifen, deren Struktur zur Kompilierzeit unbekannt sein kann — zum Beispiel, wenn:
- Code ausgeführt wird, der in einer anderen .NET-Sprache geschrieben ist (z. B. IronPython oder IronRuby), wo die Typisierung dynamisch ist.
- Mit COM-Objekten gearbeitet wird (zum Beispiel mit Microsoft Office über Interop).
- Mit dynamischen Datenstrukturen interagiert wird: zum Beispiel mit schwach formalisierten JSON-Objekten.
- Man nicht viel Boilerplate-Code schreiben möchte, sondern dem Objekt einfach „glauben“ und versuchen will, die benötigte Methode per Name aufzurufen.
Mit dem Einzug des Schlüsselworts dynamic in C# 4.0 und der DLR-Plattform wurde es möglich, Code zu schreiben, der sich „wie in Python, aber in C#“ verhält: die Prüfung erfolgt zur Laufzeit, nicht beim Kompilieren. Mit diesem magischen Wort kann man den Compiler weniger streng machen und erst zur Laufzeit prüfen, ob die benötigte Methode/Property existiert, wenn das Programm wirklich an diese Stelle gelangt.
Was ist DLR?
Dynamic Language Runtime (DLR) ist eine infrastrukturelle Erweiterung der CLR (Common Language Runtime), die die Unterstützung dynamischer Sprachen (und dynamischen Verhaltens in statischen Sprachen) ermöglicht. DLR übernimmt die Ausführung von Operationen, die der Compiler nicht vorab prüfen kann: zum Beispiel den Methodenaufruf per Namen, wenn der Typ erst zur Laufzeit bekannt wird. Dank DLR bekommt C# einen „dynamischen Modus“.
2. Das Schlüsselwort dynamic: wie es funktioniert
Unterschied zu object
Manche Anfänger denken, dass dynamic nur eine schickere Art ist, object zu schreiben und jedes Mal zu casten. Das ist nicht so.
- Eine Variable vom Typ object erfordert immer ein explizites Casting zu einem konkreten Typ, um Methoden aufzurufen (und der Compiler prüft das).
- Eine Variable vom Typ dynamic gibt dem Compiler die Anweisung: „Vertrau mir, ich weiß selbst, welcher Typ dort sein wird — wir klären das zur Laufzeit!“. Wenn die Methode nicht existiert, gibt es eine Ausnahme zur Laufzeit.
Beispiel:
// Beispiel mit object
object obj = "Hello, C#!";
// obj.ToUpper(); // Kompilierungsfehler! Der Compiler weiß nicht, dass obj ein string ist
string s = ((string)obj).ToUpper(); // Nur so
// Beispiel mit dynamic
dynamic dyn = "Hello, C#!";
string upper = dyn.ToUpper(); // Der Compiler protestiert nicht, die Prüfung erfolgt zur Laufzeit
Mit dynamic kann man auf nicht vorhandene Properties und Methoden zugreifen. Wenn sie fehlen, wird eine Ausnahme vom Typ RuntimeBinderException geworfen.
Verwendung im Beispielprogramm
Angenommen, in unserer Mini-Anwendung gibt es einen Block, der mit JSON-Daten arbeitet, deren Struktur sich zur Laufzeit ändern kann. Mit dynamic kann man auf Properties zugreifen, als ob sie immer existieren:
dynamic user = new System.Dynamic.ExpandoObject();
user.Name = "Vasya";
user.Age = 35;
Console.WriteLine($"Name: {user.Name}, age: {user.Age}");
3. DLR in Aktion: was unter der Haube passiert
Wenn eine Variable als dynamic deklariert wird, merkt sich der C#-Compiler, dass er Zugriffe auf Methoden, Properties, Indexer, Operatoren usw. für dieses Objekt nicht vorab prüfen kann.
Stattdessen fügt er an jede dynamische Stelle Code ein, der einen Aufruf an einen systemweiten „freien Dispatcher“ macht. DLR prüft: existiert diese Methode oder dieses Property, auf das wir so frei, lässig und optimistisch zugreifen wollen, tatsächlich? Wenn ja — führt es den Aufruf aus, wenn nein — wird eine Ausnahme geworfen.
Visuelles Schema
+------------------------+
| Code mit dynamic |
+----------+-------------+
|
v
+----------+-------------+
| C#-Compiler fügt ein |
| "Anfrage an DLR" |
+----------+-------------+
|
v
+----------+-------------+
| Zur Laufzeit sucht DLR |
| Methode/Property |
+----------+-------------+
|
/---------\
| |
gefunden nicht gefunden
| |
v v
Aufruf Ausnahme
der Methode (RuntimeBinderException)
DLR kümmert sich um die Suche nach Methoden und Properties im Objekt — und cached sogar häufig verwendete dynamische Aufrufe!
Ein bisschen Geschichte
DLR wurde entwickelt, um Sprachen zu unterstützen, die von Haus aus dynamisch sind: IronPython, IronRuby, und um die Integration mit dynamischen Objekten in C# zu erleichtern. Heute wird es aktiv bei der Arbeit mit Objekten wie ExpandoObject, DynamicObject, sowie mit COM und dynamisch serialisierten Daten verwendet.
4. Vergleich: static vs dynamic
Vergleichen wir, wie Aufrufe von Properties und Methoden in der statischen und der dynamischen Paradigmen aussehen:
| Methode | Deklaration | Kompilierungszeit-Prüfung | Verhalten bei Fehler |
|---|---|---|---|
|
|
Erfordert Cast zum benötigten Typ | Kompilierungsfehler oder InvalidCastException |
|
|
Nein, alles zur Laufzeit | RuntimeBinderException, falls Methode fehlt |
| statischer Typ | |
Wird vom Compiler geprüft | Kompilierungsfehler |
Beispiel zur Veranschaulichung:
// Statische Typisierung
MyUser u = new MyUser();
u.PrintInfo(); // Wird vom Compiler geprüft
// dynamic
dynamic du = new MyUser();
du.PrintInfo(); // Compiler prüft nicht, Methode wird erst zur Laufzeit gesucht
// object
object ou = new MyUser();
// ou.PrintInfo(); // Kompilierungsfehler
((MyUser)ou).PrintInfo(); // Muss explizit gecastet werden
Statische Methoden und dynamic
dynamic d = "Hello";
Console.WriteLine(d.Length); // 5 — alles gut
Console.WriteLine(d.FakeMethod()); // RuntimeBinderException zur Laufzeit
Aber Vorsicht! Wenn du dich vertippst oder ein Methodenname nicht existiert — das zeigt sich erst zur Laufzeit. Klassisches Beispiel für „Fehler in Production“.
5. Praktische Beispiele: Dynamik in realen Aufgaben
Arbeiten mit ExpandoObject
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "Steve";
person.Greet = (Action)(() => Console.WriteLine($"Hello, {person.Name}!"));
person.Greet(); // Gibt aus: Hello, Steve!
Hier haben wir zur Laufzeit das Property Name und sogar den Delegate Greet hinzugefügt. Sehr ähnlich zur Arbeit mit Objekten in JavaScript.
Binden an JSON-Strukturen
using System.Dynamic;
using Newtonsoft.Json;
string json = @"{""name"": ""Andrey"", ""age"": 30}";
dynamic obj = JsonConvert.DeserializeObject
(json); Console.WriteLine(obj.name); // Andrey Console.WriteLine(obj.age); // 30
Beachte: wenn du obj.surname schreibst — gibt es einen Fehler erst zur Laufzeit.
COM-Interop (Office Automation)
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true; // Wir "sehen" das Property, obwohl der Compiler den Typ nicht kennt
6. Nützliche Feinheiten
DLR, dynamic und Nutzung mit anderen .NET-Sprachen
- IronPython/IronRuby: Man kann Objekte dieser Sprachen instanziieren und ihre Methoden aus C# über dynamic aufrufen.
- COM-Objekte: automatische Wrapper für Methoden und Properties über dynamic.
- Eigene dynamische Objekte: Wenn man das Interface IDynamicMetaObjectProvider implementiert, kann man seine Klasse für dynamische Aufrufe fit machen.
Beispiel Integration mit IronPython
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
ScriptEngine engine = Python.CreateEngine();
dynamic py = engine.Execute("class Test: pass\nTest()");
py.x = 42;
Console.WriteLine(py.x); // 42
Wie man eigene dynamische Typen implementiert
Normalerweise ist es einfacher, von DynamicObject zu erben.
using System.Dynamic;
class MyDynamic : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "Ping")
{
result = "Pong!";
return true;
}
result = null;
return false;
}
}
dynamic d = new MyDynamic();
Console.WriteLine(d.Ping); // Pong!
Console.WriteLine(d.Miss); // null (wirft keine Ausnahme)
Wann dynamic schadet
- Vermindert die Performance (dynamische Aufrufe sind langsamer als statische).
- Erhöht die Komplexität beim Debuggen und Warten des Codes.
- Raubt den meisten IDE-Vorteilen: Autocomplete, Refactoring, Usage-Search.
- Kann zu Fehlern führen, die erst beim Nutzer oder auf dem Server sichtbar werden.
Wann man dynamic wirklich nutzen sollte
- Integration mit externen Bibliotheken, bei denen es unmöglich oder unpraktisch ist, Wrapper zu schreiben (COM, Python, JavaScript).
- Manipulation schwach getypteter Strukturen (JSON/XML/ExpandoObject).
- Zum Schreiben von Frameworks, Metaprogrammierung und flexiblen APIs, die von Natur aus dynamisch sein sollen.
In allen anderen Fällen ist es besser, statische Typen zu verwenden und sich über die Hilfe des Compilers zu freuen :-)
7. Typische Fehler bei der Arbeit mit dynamic und DLR
Fehler Nr.1: Zugriff auf nicht existierende Mitglieder.
Der Aufruf einer nicht existierenden Methode oder Property bei dynamic führt zur RuntimeBinderException zur Laufzeit, was während der Entwicklung schwer zu fangen ist.
Fehler Nr.2: Ignorieren von Typprüfungen.
Ohne Typchecks vor dem Aufruf (z. B. if (obj is IDictionary<string, object>)) kann man unerwartete Laufzeitfehler bekommen.
Fehler Nr.3: Verwendung von dynamic in öffentlichen APIs.
Dynamische Typen erschweren die Wartung des Codes, weil IDEs und Analyzer die richtigen Mitglieder nicht vorschlagen können, was die Lesbarkeit mindert.
Fehler Nr.4: Missbrauch von dynamic in Performance-kritischem Code.
Dynamische Aufrufe sind langsamer als statische und erhöhen den Overhead. Nutze dynamic nur wenn nötig.
GO TO FULL VERSION