Innovationen

Das Schlüsselwort „dynamic“ in C# 4.0

Dino Esposito

Dino EspositoDie Einführung der statischen Typenüberprüfung hat einen wichtigen Meilenstein in der Geschichte der Programmiersprachen dargestellt. In den 1970er Jahren haben Sprachen wie Pascal und C damit begonnen, statischen Typen und starke Typenüberprüfung zu erzwingen. Bei der statischen Typenüberprüfung erzeugt der Compiler einen Fehler für jeden Aufruf, der kein Methodenargument mit dem geeigneten Typ übergibt. Auch können Sie einen Compilerfehler erwarten, wenn Sie versuchen, eine fehlende Methode für eine Typinstanz aufzurufen.

Mit der Zeit wurden andere Sprachen entwickelt, die einem entgegengesetzten Ansatz folgen: die dynamische Typenüberprüfung. Die dynamische Typenüberprüfung widerspricht der Idee, dass der Typ einer Variablen beim Kompilieren statisch bestimmt und nicht geändert werden kann, solange die Variable im Bereich liegt. Beachten Sie aber, dass die dynamische Typenüberprüfung keine uneingeschränkte Freiheit zum Mischen von Typen gewährt und vorgibt, sie wären identisch. Beispielsweise können Sie auch mit der dynamischen Typenüberprüfung keinen booleschen Wert zu einer ganzen Zahl hinzufügen. Der Unterschied bei der dynamischen Typenüberprüfung ist, dass die Überprüfung bei der Programmausführung und nicht bei der Kompilierung erfolgt.

Statische oder dynamische Typisierung

Visual Studio 2010 und C# 4.0 bieten ein neues Schlüsselwort, „dynamic“, das dynamische Typisierung für eine traditionell statisch typisierte Sprache ermöglicht. Bevor wir uns nun mit den dynamischen Aspekten von C# 4.0 beschäftigen, sollten wir die grundlegende Terminologie klären.

Lassen Sie uns eine Variable als einen Speicherort definieren, der auf Werte mit einem bestimmten Typ beschränkt ist. Geben wir jetzt vier grundlegende Eigenschaften einer statisch typisierten Sprache an:

  • Jeder Ausdruck hat einen Typ, der zur Kompilierzeit bekannt ist.
  • Variablen sind auf einen Typ beschränkt, der zur Kompilierzeit bekannt ist.
  • Der Compiler garantiert, dass Typbeschränkungen auf Zuweisungen von Ausdrücken in Variablen die Beschränkungen für die Variablen erfüllen.
  • Semantische Analysen, wie die Überladungsauflösung, erfolgen zur Kompilierzeit, und die Ergebnisse werden in die Assembly integriert.

Eine dynamische Sprache weist entgegengesetzte Eigenschaften auf. Nicht jeder Ausdruck hat einen Typ, der zur Kompilierzeit bekannt ist, und auch nicht alle Variablen sind es. Etwaige Speicherbeschränkungen werden zur Laufzeit überprüft und zur Kompilierzeit ignoriert. Semantische Analysen werden zur Laufzeit ausgeführt.

Mit einer statisch typisierten Sprache können einige Vorgänge aber dynamisch sein. Mit dem Umwandlungsoperator können Sie versuchen, eine Typkonvertierung als Laufzeitvorgang vorzunehmen. Die Konvertierung ist Bestandteil des Programmcodes, und Sie können die vom Umwandlungsoperator ausgedrückte Semantik als „dynamische Überprüfung der Gültigkeit dieser Konvertierung zur Laufzeit“ zusammenfassen.

Aber hinsichtlich der Attribute wie dynamisch und statisch (oder eben stark und schwach): Heute gelten sie eher für einzelne Features von Programmiersprachen und nicht für die Sprache als Ganzes.

Betrachten wir jetzt kurz Python und PHP. Beides sind dynamische Sprachen, in denen Sie Variablen verwenden können und in denen die Laufzeitumgebung den tatsächlich gespeicherten Typ ermitteln kann. Aber bei PHP können Sie z. B. ganze Zahlen und Zeichenfolgen in derselben Variablen im selben Bereich speichern. In dieser Hinsicht ist PHP (wie JavaScript) eine schwach typisierte, dynamische Sprache.

Im Gegensatz dazu gibt Python Ihnen nur eine Gelegenheit, den Typ einer Variablen festzulegen. Dadurch ist diese Sprache stärker typisiert. Sie können den Typ einer Variablen dynamisch zuweisen, sodass die Laufzeit ihn vom zugewiesenen Wert ableitet. Danach ist es aber nicht mehr zulässig, einen beliebigen Wert eines nicht geeigneten Typs in der Variablen zu speichern.

Dynamische Typen in C#

C# 4.0 weist Features auf, mit denen die Sprache gleichzeitig dynamisch und statisch sowie schwach und stark typisiert ist. Zwar war C# anfänglich eine statisch typisierte Sprache, sie wird aber in beliebigen Kontexten, in denen Sie beispielsweise das folgende Schlüsselwort „dynamic“ verwenden, zu einer dynamisch typisierten Sprache:

dynamic number = 10;
Console.WriteLine(number);

Und da das Schlüsselwort „dynamic“ kontextbezogen und nicht reserviert ist, gilt dies auch, wenn vorhandene Variablen oder Methoden den Namen „dynamic“ aufweisen.

Beachten Sie, dass C# 4.0 die Verwendung von „dynamic“ nicht erzwingt, so wie auch C# 3.0 die Verwendung von „var“, „lambdas“ oder Objektinitialisierern nicht erzwungen hat. C# 4.0 bietet das neue Schlüsselwort „dynamic“ speziell dafür, den Umgang mit einigen gut bekannten Szenarien zu erleichtern. Die Sprache bleibt im Wesentlichen statisch typisiert, auch wenn jetzt die Möglichkeit besteht, effektiver mit dynamischen Objekten zu interagieren.

Warum sollte ein dynamisches Objekt verwendet werden? Erstens kennen Sie den Typ des betreffenden Objekts möglicherweise nicht. Möglicherweise gibt es Hinweise, aber keine Sicherheit, dass eine bestimmte Variable statisch typisiert werden sollte. Dies passiert in vielen gängigen Situationen, beispielsweise bei der Arbeit mit COM-Objekten oder bei der Verwendung von Reflektion zum Abrufen von Instanzen. In diesem Kontext erleichtert das Schlüsselwort „dynamic“ den Umgang mit einigen Situationen. Mit „dynamic“ geschriebener Code kann besser gelesen und geschrieben werden, sodass eine Anwendung entsteht, die besser verständlich und verwaltbar ist.

Zweitens weist Ihr Objekt möglicherweise eine veränderliche Natur auf. Möglicherweise arbeiten Sie mit Objekten, die in dynamischen Programmierumgebungen wie IronPython und IronRuby erstellt wurden. Sie können diese Funktionalität aber auch für HTML-DOM-Objekte (abhängig von der expando-Eigenschaft) und die Microsoft .NET Framework 4-Objekte verwenden, die speziell als dynamische Objekte erstellt wurden.

Verwenden des Schlüsselworts „dynamic“

Sie sollten unbedingt das Konzept beachten, dass im C#-Typsystem „dynamic“ ein Typ ist. Dies ist eine sehr spezielle Bedeutung, aber es ist definitiv ein Typ und muss auch so behandelt werden. Sie können „dynamic“ als Typ einer Variablen angeben, die Sie deklarieren, als Typ von Elementen in einer Sammlung oder als Rückgabewert einer Methode. Sie können „dynamic“ auch als Typ eines Methodenparameters verwenden. Im Gegenzug können Sie „dynamic“ nicht mit dem typeof-Operator verwenden, und Sie können es nicht als Basistyp einer Klasse verwenden.

Im folgenden Code wird veranschaulicht, wie eine dynamische Variable im Text einer Methode deklariert wird:

public void Execute()  { 
  dynamic calc = GetCalculator();
  int result = calc.Sum(1, 1);
}

Wenn Sie über ausreichend Informationen über den Typ des von der GetCalculator-Methode zurückgegebenen Objekts verfügen, können Sie die Variable „calc“ mit dem Typ deklarieren. Sie können die Variable aber auch als „var“ deklarieren, und der Compiler ermittelt dann die genauen Details. Bei der Verwendung von „var“ oder einem explizit statischen Typ müssten Sie sicher sein, dass eine Sum-Methode in dem Vertrag vorhanden ist, der vom von GetCalculator zurückgegebenen Typ verfügbar gemacht wird. Ist die Methode nicht vorhanden, wird ein Compilerfehler angezeigt.

Mit „dynamic“ verschieben Sie die Entscheidung über die Richtigkeit des Ausdrucks auf die Ausführungszeit. Der Code wird kompiliert und zur Laufzeit aufgelöst, sofern eine Sum-Methode für den Typ verfügbar ist, der in der Variablen „calc“ gespeichert ist.

Sie können mit dem Schlüsselwort auch eine Eigenschaft für eine Klasse definieren. So statten Sie das Element mit einem gewünschten Sichtbarkeitsmodifizierer aus, beispielsweise „public“, „protected“ und sogar „static“.

Abbildung 1 veranschaulicht die Vielseitigkeit des Schlüsselworts „dynamic“. Im Hauptprogramm habe ich eine dynamische Variable mit dem Rückgabewert eines Funktionsaufrufs instanziiert. Das wäre nichts Besonderes, wenn die Funktion nicht ein dynamisches Objekt empfangen und zurückgeben würde. Es ist interessant zu sehen, was passiert, wenn Sie wie im Beispiel eine Zahl übergeben und dann versuchen, sie in der Funktion zu verdoppeln.

Abbildung 1 Verwenden von „dynamic“ in der Signatur einer Funktion

class Program {
  static void Main(string[] args) {
    // The dynamic variable gets the return 
    // value of a function call and outputs it.
    dynamic x = DoubleIt(2);
    Console.WriteLine(x);

    // Stop and wait
    Console.WriteLine(“Press any key”);
    Console.ReadLine();
  }

  // The function receives and returns a dynamic object 
  private static dynamic DoubleIt(dynamic p) {
    // Attempt to "double" the argument whatever 
    // that happens to produce
    
    return p + p;
  }
}

Wenn Sie den Wert 2 eingeben und diesen Code ausprobieren, erhalten Sie den Wert 4. Wenn Sie 2 als Zeichenfolge eingeben, erhalten Sie stattdessen 22. In der Funktion wird der +-Operator basierend auf dem Laufzeittyp der Operanden dynamisch aufgelöst. Wenn Sie den Typ zu „System.Object“ ändern, kommt es zu einem Kompilierfehler, da der +-Operator nicht für „System.Object“ definiert ist. Mit dem Schlüsselwort „dynamic“ sind Szenarien möglich, die ohne dieses Schlüsselwort nicht möglich wären.

„dynamic“ oder „System.Object“

Vor .NET Framework 4 konnte eine Methode nur unterschiedliche Typen gemäß unterschiedlichen Bedingungen zurückgeben, indem auf eine allgemeine Basisklasse zurückgegriffen wurde. Sie haben dieses Problem möglicherweise mit „System.Object“ gelöst. Eine Funktion, die „System.Object“ zurückgibt, macht dem Aufrufer eine Instanz verfügbar, die in fast alles umgewandelt werden kann. Warum ist die Verwendung von „dynamic“ also besser als die Verwendung von „System.Object“?

In C# 4 wird der eigentliche Typ hinter der Variablen, die dynamisch deklariert wird, zur Laufzeit aufgelöst. Der Compiler nimmt einfach an, dass das in einer Variablen dynamisch deklarierte Objekt jeden Vorgang unterstützt. Dies bedeutet, dass Sie tatsächlich Code schreiben können, der eine Methode für ein Objekt aufruft, von dem Sie annehmen, dass es zur Laufzeit vorhanden ist. Dies ist im Folgenden Beispiel veranschaulicht:

dynamic p = GetSomeReturnValue();
p.DoSomething();

In C# 4.0 gibt der Compiler bei diesem Code keine Fehlermeldungen aus. Mit „System.Object“ würde der gleiche Code nicht kompiliert werden. Damit er funktioniert, müssten Sie noch Reflektionen oder Umwandlungen einbinden.

„var“ oder „dynamic“

Die Schlüsselwörter „var“ und „dynamic“ sind nur scheinbar ähnlich. Mit „var“ wird angegeben, dass der Typ der Variablen auf den Kompilierzeittyp des Initialisierers festgelegt werden muss.

Dagegen bedeutet „dynamic“, dass der Typ der Variablen der in C# 4.0 verfügbare dynamische Typ ist. Letztlich haben „dynamic“ und „var“ eine gegensätzliche Bedeutung. Mit „var“ wird die statische Typisierung verstärkt und verbessert. Das Ziel ist sicherzustellen, dass der Typ einer Variablen vom Compiler abgeleitet wird, indem der genaue Typ analysiert wird, der vom Initialisierer zurückgegeben wird.

Mit dem Schlüsselwort „dynamic“ wird die statische Typisierung insgesamt vermieden. Bei Verwendung in einer Variablendeklaration weist „dynamic“ den Compiler an, den Typ der Variablen überhaupt nicht zu ermitteln. Der Typ soll der Typ sein, der er zur Laufzeit ist. Mit „var“ ist der Code statisch typisiert, wie er es auch wäre, wenn Sie sich für den klassischen Ansatz der Verwendung expliziter Typen in einer Variablendeklaration entschieden hätten.

Ein weiterer Unterschied zwischen den beiden Schlüsselwörtern ist, dass „var“ nur in einer lokalen Variablendeklaration enthalten sein kann. Sie können mit „var“ keine Eigenschaft für eine Klasse definieren, und Sie können damit auch keinen Rückgabewert oder Parameter einer Funktion angeben.

Als Entwickler verwenden Sie das Schlüsselwort „dynamic“ bei Variablen, die wahrscheinlich Objekte mit einem unbestimmten Typ enthalten. Dies sind beispielsweise Objekte, die von einer COM- oder DOM-API zurückgegeben werden, und die von einer dynamischen Sprache (wie IronRuby), von Reflektionen und von Objekten, die dynamisch in C# 4.0 mit den neuen Erweiterungsfunktionen erstellt wurden, stammen.

Der Typ „dynamic“ umgeht jedoch keine Typenüberprüfungen. Er verschiebt sie nur alle in die Laufzeit. Wenn zur Laufzeit inkompatible Typen ermittelt werden, werden Ausnahmen ausgelöst.

Dino Esposito ist Autor des bald bei Microsoft Press erscheinenden Titels „Programming Microsoft ASP.NET MVC“ und Mitverfasser des Titels „Microsoft .NET: Architecting Applications for the Enterprise“ (Microsoft Press, 2008). Esposito lebt in Italien und ist ein weltweit gefragter Referent bei Branchenveranstaltungen. Sie finden seinen Blog unter weblogs.asp.net/despos.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels:Eric Lippert