Dynamische Typisierung in C# 👍 👎

Im Artikel zur impliziten Typisierung hatte ich bereits auf einen späteren Artikel zum Thema dynamische Typisierung verwiesen, was mit diesem Beitrag nun auch erfolgen soll.

Seit .NET 4.0 enthält das Framework die sog. Dynamic Language Runtime, was sich bei C# auf den ersten Blick durch das neue Schlüsselwort dynamic, die Basisklasse DynamicObject und das ExpandoObject mit entsprechender Funktionalität bemerkbar macht.

Die DLR ermöglicht zwar "auch" interessante Aspekte im Hinblick auf die Integration dynamisch typisierter Programmiersprachen wie beispielsweise Python (→ IronPython) oder Ruby (→ IronRuby) in das .NET-Framework, ich möchte in diesem Beitrag aber dennoch bei den Möglichkeiten in C# selbst bleiben. Ein paar Gedanken meinerseits zur Typisierung im Allgemeinen sollen in einem späteren Beitrag folgen.

Wie vielen Lesern dieses Blogs bekannt sein dürfte, handelt es sich bei C# um eine grundsätzlich statisch typisierte Programmiersprache, d. h. die Typen aller Variablen sind bereits zum Zeitpunkt der Kompilierung bekannt, was nicht zuletzt auch ermöglicht, dass euch Visual Studio hilfreiche Hinweise schon während der Entwicklung geben kann.

Durch die DLR wird von diesem Prinzip (glücklicherweise Smiley: winking_grinning) nicht grundsätzlich abgewichen, jedoch stellt sich euch die Möglichkeit, von diesem Vorgehen explizit abzuweichen, indem ihr als Typ dynamic deklariert. Dadurch wird die statische Typprüfung deaktiviert und auf die Laufzeit verschoben. Das bedeutet, dass ihr einer Variable dieses Typs beliebige – also auch gar nicht existente – Eigenschaften zuweisen oder darauf Methoden aufrufen könnt, im Problemfall jedoch zur Laufzeit eine entsprechende Ausnahme erhaltet.

Wir beginnen – wie üblich – mit einem kleinen Code-Beispiel zur Verdeutlichung:
Dynamische Variablen erzeugen
0102
dynamic a = 123;dynamic b = "123";
Nun können wir relativ "normal" damit arbeiten, beispielsweise die Werte in einer MessageBox ausgeben:
Dynamische Variablen verwenden (1)
0102030405060708
MessageBox.Show(a.ToString());  //: "123"MessageBox.Show(b.ToString());  //: "123"/** * C# konvertiert hier nicht in eine mathematische Addition, * da "b" zur Laufzeit als Zeichenkette interpretiert wurde.**/MessageBox.Show(a + b);  //: "123123"
Soweit hätte das natürlich auch ohne dynamic funktioniert, der interessante Aspekt ist jedoch, dass sich beispielsweise auch folgender Code kompilieren lässt – Visual Studio zeigt euch bei Eingabe des Punktes (→ IntelliSense) nur mehr "(dynamischer Ausdruck) Der Vorgang wird zur Laufzeit aufgelöst." an:
Dynamische Variablen verwenden (2)
0102
MessageBox.Show(b.Replace("2", "4"));  //: "143"MessageBox.Show(a.Replace("2", "4"));  // Ausnahme!
Ohne dynamic hätte euch Visual Studio bereits während der Entwicklung angezeigt, dass Replace(…) für den Datentyp int nicht zur Verfügung steht (bzw. erst gar nicht zur Auswahl angeboten) und die Kompilierung mit einem Fehler abgebrochen.

Im Gegensatz zu var kann sich der Typ unserer Variable nun auch während der Laufzeit ändern:
Änderung des Typs zur Laufzeit
01020304050607
MessageBox.Show(a.GetType().ToString());  //: "System.Int32"MessageBox.Show(a.ToString());  //: "123"
a = "test";

MessageBox.Show(a.GetType().ToString()); //: "System.String"MessageBox.Show(a.ToString()); //: "test"
Dies entspricht schon sehr dem Verhalten, wie man es von Sprachen wie PHP kennt und bringt die entsprechenden Vorteile, aber eben auch gravierende Nachteile mit sich. Ob eine Operation zu Problemen führt, erkennt man nunmehr erst zur Laufzeit – und mit etwas Pech auch nicht sofort, da die ungültige Operation möglicherweise nicht sofort zur Verwendung kommt, mit statischer Typisierung aber dennoch bereits zur Entwicklungszeit von Visual Studio darauf hingewiesen worden wäre, dass dies niemals möglich sein wird.

Wenn das nun neben ein bisschen Bequemlichkeit beim Tippen doch eher Probleme einführt, stellt sich natürlich die berechtigte Frage, wozu man so etwas verwenden sollte. Hauptsächlich handelt es sich tatsächlich um ein Mittel, dynamische Sprachen in das .NET-Framework zu integrieren, welche nun einmal auf diesem Konstrukt basieren. Auch die Verwendung entsprechender Programmbibliotheken, um beispielsweise einfach über XML-Knoten zu navigieren (man denke an dieser Stelle auch an das DOM) sind sicherlich eine praktikable Idee. Für die reine Bequemlichkeit ist implizite Typisierung jedoch definitiv besser geeignet.

Ich würde auf jeden Fall dringend davon abraten, unter C# selbst exzessiven und vor allem unüberlegten Einsatz davon zu machen. Natürlich kann man sich dadurch z. B. gewisse Umstände, die Reflektion mit sich bringt, ersparen, dennoch würde ich hier nach wie vor zu einem sauberen, auf Schnittstellen basierten Modell raten. Dadurch, das alles "irgendwie" aufgerufen werden kann, handelt man sich nicht selten schwer auffindbare und auch lösbare Fehler ein. Insbesondere sollte bedacht werden, dass das .NET-Framework beim Aufruf entsprechender Funktionalität auch immer passende Datentypen erwartet; Folgendes ergibt beispielsweise umgehend einen Laufzeitfehler:
Gefahren bei der Interaktion mit dem .NET-Framework
010203
dynamic c = 5;
MessageBox.Show(c); // Ausnahme!
Das Problem ist nämlich, dass Show(…) eine Zeichenkette erwartet; bei b hätte es auch so funktioniert, c muss jedoch zwingend mit ToString() aufgerufen werden. Mit statischer Typprüfung sind das alles Probleme, die man bereits beim Schreiben des Quelltextes erfährt. Mit dynamischer Typprüfung möglicherweise aber erst nach einigen Stunden oder gar noch längerer Laufzeit, wenn die Stelle zum ersten Mal zur Ausführung kommt.

Da wir nun umfassend auf die potentiellen Gefahren eingegangen sind und erste Beispiele betrachtet haben, möchte ich noch einmal kurz auf DynamicObject und ExpandoObject zurückkommen und dazu jeweils ein kleines Anwendungsbeispiel demonstrieren. Wir beginnen mit dem ganz einfachen ExpandoObject und fügen diesem Eigenschaften und Verhalten hinzu:
ExpandoObject definieren und verwenden
01020304050607080910111213141516
dynamic obj = new ExpandoObject();{      // Eigenschaften setzen    obj.FirstName = "Holger";    obj.LastName = "Stehle";
obj.Age = 24;}
// Verhalten definierenobj.GetName = new Func<string>(() => { // der Rückgabetyp könnte auch "dynamic" lauten return obj.LastName + ", " + obj.FirstName;});
// Name und Alterstring person = obj.GetName() + " (" + obj.Age + ")"; //: "Stehle, Holger (24)"
Als nächstes wollen wir noch einen (kleinen) Blick auf das DynamicObject werfen, von welchem wir erben und dabei spezifisches Verhalten für bestimmte Aktionen festlegen können. Konkret möchten wir den Zugriff auf Eigenschaften unabhängig von Groß-/Kleinschreibung ermöglichen:
DynamicObject erben und verwenden
01020304050607080910111213141516171819202122232425262728293031
  // Klasse ableiten und Verhalten anpassenpublic class MyDynamicObject : DynamicObject {    private Dictionary<string,object> data = new Dictionary<string,object>();

// Eigenschaft abrufen public override bool TryGetMember(GetMemberBinder binder, out object value) { return data.TryGetValue(binder.Name.ToLower(), out value); }
// Eigenschaft setzen public override bool TrySetMember(SetMemberBinder binder, object value) { data[binder.Name.ToLower()] = value;
return true; }}
// Klasse verwendendynamic obj = new MyDynamicObject();{ // Eigenschaften setzen obj.FirstName = "Holger"; obj.lastName = "Stehle";}
string firstName = obj.FirstName; //: "Holger"string firstName = obj.firstName; //: "Holger"string lastName = obj.lastName; //: "Stehle"string lastName = obj.LastName; //: "Stehle"
Mein Fazit: Sicherlich wünscht man sich das eine oder andere Mal während der Entwicklung ein wenig mehr Dynamik, aber dafür bezahlt man einen nicht unerheblichen Preis zum Nachteil der Wartbarkeit. Zur Implementierung von Standardaufgaben sollte das unter C# nicht das erste Mittel der Wahl sein, eher eines der letzten – C# bietet hier für gewöhnlich mächtige und vor allem typsichere Alternativen. Ja, das ist durchaus mit etwas mehr Schreibarbeit verbunden.

Wie üblich hält das MSDN weitere Hinweise zur Verwendung parat.


Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche