Kategorien

Navigation

Feeds/Meta

Blog RSS Feed
Kommentare RSS Feed

Manuel Josupeit-Walter - XING Profil
Manuel Josupeit-Walter - Amazon Wunschliste
Manuel Josupeit-Walter - XMPP (Jabber)
Manuel Josupeit-Walter - ICQ
Manuel Josupeit-Walter - Skype

Bloggeramt.de
BlogAlm
Add to Technorati Favorites

SpamPoison

josupeit.com > Weblog


C# 4.0 dynamic-Schlüsselwort

Ich entwickle Software nun schon seit Version 1.1 des Frameworks in .net und mit jeder Version kommen mehr oder weniger bahnbrechende Sprachfeatures hinzu. Generics, Linq oder lokale Typinferenz sind nur einige Buzzwords, die mir auf die Schnelle in diesem Zusammenhang einfallen. Seit heute jedoch bin ich Fan eines weiteren Sprachfeatures, das unter anderem der Interoperabilität mit untypisierten Sprachen, wie Javascript geschuldet ist: Des dynamic-Schlüsselworts.

Mit Version 4.0 des .net-Frameworks hat Microsoft die sogenannte DLR, die Dynamic Runtime Language eingeführt und damit seine Sprachen um dynamische Typisierung erweitert. Bitte nicht falsch verstehen, Variablen, die als dynamic deklariert werden sind nicht etwa untypisiert, vielmehr geht der Compiler zunächst davon aus, dass jede beliebige Operation auf einem dynamic-Objekt ausgeführt werden kann. Etwaige Probleme entstehen erst zur Laufzeit (Duck Typing ist z.B. nicht "einfach so" möglich).

Während früher z.B. viel reflektiver Code notwendig war, um zur Laufzeit auf einem Objekt beliebigen Typs Methoden auszuführen, wird das Leben mit Hilfe des dynamic-Schlüsselwortes weitaus leichter. Das folgende Beispiel ist dem MSDN-Magazine entnommen: Dynamic .NET - Understanding the Dynamic Keyword in C# 4.0

object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod, null, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

Vereinfachung durch die Nutzung der dynamischen Typisierung:

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

Interessanter finde ich jedoch, dass durch dieses Schlüsselwort auch dynamisches Dispatching ermöglicht wird! Das folgende Beispiel stellt eine Factory-Klasse dar, die verschiedene "Personen" instanziieren, und anhand mehrerer Methodenüberladungen einige Eigenschaften dieser "Personen" auf Standardwerte setzen soll. Dieses Beispiel funktioniert aufgrund des statischen Dispatchings jedoch nicht:

public static class PersonFactory {
  public static TPerson CreatePerson<TPerson>() where TPerson : Person {
    TPerson p = (TPerson)Activator.CreateInstance(typeof(TPerson), new object[] { });
    InitializePerson(p); // Wird immer die erste Überladung aufrufen
  }

  public static void InitializePerson(Person p) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Student s) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Teacher t) {
    t.Students = new List<Student>();
  }
}

public class Person {
  public string LastName { get; set; }
  public string FirstName { get; set; }
}

public class Student : Person {
  /* This is just a stub */
}

public class Teacher : Person {
  public IList<Student> Students { get; set; }
}

Da der statische Typ von p in der Methode CreatePerson der PersonFactory Person ist, wird immer die erste Überladung der InitializePerson-Methode aufgerufen werden. Das steht bereits zum Zeitpunkt der Kompilierung fest. Die einzige Möglichkeit bisher, die richte Überladung ohne dynamisches Dispatching aufzurufen, ist der Umweg über sog. Double Dispatching:

public static class PersonFactory {
  public static TPerson CreatePerson<TPerson>() where TPerson : Person {
    TPerson p = (TPerson)Activator.CreateInstance(typeof(TPerson), new object[] { });
    p.InitializePerson();
  }

  public static void InitializePerson(Person p) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Student s) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Teacher t) {
    t.Students = new List<Student>();
  }
}

public abstract class Person {
  public string LastName { get; set; }
  public string FirstName { get; set; }

  public abstract void InitializePerson();
}

public class Student : Person {
   public override void InitializePerson() {
    PersonFactory.InitializePerson(this);
  }
}

public class Teacher : Person {
  public IList<Student> Students { get; set; }

   public override void InitializePerson() {
    PersonFactory.InitializePerson(this);
  }
}

Durch verwendung des doppelten Dispatchings wird nun die korrekte Überladung der InitializePerson-Methode innerhalb der PersonFactory aufgerufen, allerdings muss ab jetzt jede von Person erbende Klasse die Methode InitializePerson mit immer dem selben Aufruf implementieren, was zu redundantem Code führt, der darüber hinaus auch noch leicht vergessen werden kann und dessen Sinn sich für Laien oft nicht erschließt. Sollte künftig jedoch eine weitere Vererbung hinzukommen, wie z.B. der SuperTeacher, der die Klasse Teacher erweitert und nicht mehr vom Compiler gezwungen wird, die Methode InitializePerson zu überschreiben, wird wiederum immer nur die Methodenüberladung für Teacher aufgerufen werden, bis die Methode auch von SuperTeacher überschrieben wird. Der Code in der Methode ist jedoch der selbe, wie der der Basisklasse Teacher. :-(

Die Lösung für dieses Dilemma ist das dynamic-Schlüsselwort, denn dadurch wird erst zur Laufzeit und nicht bereits bei der Kompilierung entschieden, welche Methode aufgerufen wird:

public static class PersonFactory {
  public static TPerson CreatePerson<TPerson>() where TPerson : Person {
    dynamic p = Activator.CreateInstance(typeof(TPerson), new object[] { });
    InitializePerson(p); // Wird die beste Überladung aufrufen!!
  }

  public static void InitializePerson(Person p) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Student s) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Teacher t) {
    t.Students = new List<Student>();
  }
}

public class Person {
  public string LastName { get; set; }
  public string FirstName { get; set; }
}

public class Student : Person {
  /* This is just a stub */
}

public class Teacher : Person {
  public IList<Student> Students { get; set; }
}

Fertig. Geil! 8-)
















Bisherige Kommentare:

Keine