Home  |  Über den Autor  |  Impressum  |  .NET Developer Group Braunschweig  |  Hönigsberg & Düvel

RSS 2.0 | Atom 1.0 | CDF | Send mail to the author(s)  
 Monday, July 18, 2011

Auf meiner Rückfahrt vom .NET Open Space Süd hat Alexander Groß in einer ad-hoc Session Lars Keller und mir Ruby etwas näher gebracht. Am Ende der Session sagte Alex, dass es in Ruby ein Gem (eine Art Package oder Modul) gibt, mit dem man dynamisch auf XML zugreifen kann.

Da es seit C# 4.0 Unterstützung für Dynamische Sprachen gibt, habe ich mich gefragt, ob das dann nicht auch mit C# geht. Um es kurz zu machen: Es geht! Wie das Ganze funktioniert zeige ich Schritt für Schritt.

Ziel
Erst mal möchte ich noch ein bisschen genauer spezifizieren was ich eigentlich umsetzen wollte. Stellen wir uns folgende XML-Datei vor:

<Books>
  <Book>
    <Title>Ruby ist dynamisch</Title>
  </Book>
  <Book>
    <Title>C# auch</Title>
  </Book>
</Books>


Wäre es nicht cool, wenn wir im Code (unter Voraussetzung, dass wir besagte XML-Struktur kennen) einfach schreiben könnten:

dynamic bookstore = new DynamicXDocument(@"c:\bookstore.xml");  

foreach
(var book in bookstore.books) { Console.WriteLine("Titel: " + book.Title); }


“books” und “book” sollen dabei ebenfalls dynamisch sein. Es gibt also nirgendwo eine Klasse „Book“ mit der Eigenschaft „Title“.

Wie bekommen wir das hin? Fest steht schon einmal, dass bookstore durch das Keyword dynamic definiert werden muss, da wir sonst nicht auf Eigenschaften zugreifen können, die zur Compile-Zeit noch nicht vorhanden sind. Aber wie ist die noch nicht existierende Klasse DynamicXDocument zu implementieren?

Bei meinen Recherchen bin ich auf den Artikel [1] von Mario Meir-Huber und das dynamische Objekt „ExpandoObject“ gestoßen. Einem ExpandoObject kann man dynamisch Properties hinzufügen, die  intern in einem Dictionary gespeichert werden. Das geht schon in die richtige Richtung, allerdings sollen unsere Eigenschaften nicht aus einem Dictionary kommen, sondern aus einer XML-Datei.

Um selber eine dynamische Klasse zu erstellen, muss man das Interface IDynamicMetaObjectProvider implementieren. Dieses Interface hat allerdings nur eine Methode, die genau einen Parameter hat:


DynamicMetaObject GetMetaObject(Expression parameter)

Wer sich nicht mit ExpressionTrees rumschlagen möchte, kann glücklicherweise auf die Klasse DynamicObject zurückgreifen, die die Auswertung des ExpressionTrees für einen übernimmt und komfortablere/spezialisierte Methoden zur Überschreibung zu Verfügung stellt.


Die Erkenntnis, die wir bis jetzt haben ist, wenn wir ein dynamisches Objekt erzeugen wollen, können wir eine Klasse von DynamicObject ableiten und müssen nur die entsprechenden Methoden zur Erkennung der Aufrufe auf das Objekt überschreiben.


Okay, schauen wir mal was sich daraus machen lässt. Schauen wir uns noch mal den gewünschten Code an:

dynamic bookstore = new DynamicXDocument(@"c:\bookstore.xml");

foreach(var book in bookstore.books)
{
    Console.WriteLine("Titel: " + book.Title);
}

Wir benötigen also als erstes eine Klasse DynamicXDocument, die von DataObject ableitet und einen Konstruktor hat, der einen Dateipfad übergeben bekommt. Da wir als erstes auf bookstore.books in einer foreach-Schleife zugreifen, muss unsere Klasse auch noch das Interface IEnumerable implementieren.

public class DynamicXDocument : DynamicObject, IEnumerable
{
    private XElement _node;
    
    public DynamicXDocument(string filePath)
    {
        _node = (XElement)XDocument.Load(File.OpenRead(filePath)).FirstNode;
    }

  private DynamicXDocument(XElement node)   
{    
_node = node;  
}

    public IEnumerator GetEnumerator()
    {
        if (_node == null)
        {
            yield break;
        }
 

        foreach (var element in _node.Elements())
        {
            dynamic dynElement = new DynamicXDocument(element);
            yield return dynElement;
        }
    }
}

Was ich im Enumerator mache, ist nix weiter, als alle Unterelemente von “Books” als neue Instanzen  meiner dynamischen Klasse zurückzugeben, die als Hauptknoten das XElement “Book” haben. Bleibt nur noch der Zugriff auf “book.Title”. Um auf diese “virtuelle” Eigenschaft zuzugreifen können wir folgende Methode von DynamicObject überschreiben:

TryGetMember(GetMemberBinder binder, out object result)

 

Im Parameter “binder” werden Informationen übergeben, was genau aufgerufen wurde. Mit binder.Name kann man z.B. auf den Namen des aufgerufenen Properties (in unserem Fall “Title”) zugreifen. Eine simple Implementierung sieht wie folgt aus:

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var node = from n in _node.Elements(binder.Name) select n;    
    
    if (node.Count() == 0)     
    {      
        result = null;         
        return false;     
    }     
 
    XElement element = node.First();     
    result = element.Value;     
    return true;

}


So einfach ist das. Im angehängten Beispielprojekt habe ich noch Rekursion und Attribute eingebaut. Später werde ich vielleicht noch das Hinzufügen, Ändern, Löschen und Speichern der Daten implementieren. Etwas Fehlerbehandlung könnte auch nicht schaden ;-)

DOWNLOAD


Fazit
Bisher hatte ich gedacht, dass das neue Schlüsselwort „dynamic“ nur dazu zu gebrauchen ist einfacher auf COM-Objekte zuzugreifen. Da habe ich mich gewaltig geirrt. Im Namespace System.Dynamics steckt noch wesentlich mehr, was es lohnt zu erkunden. Wie man an dem XML-Beispiel sehen kann, kann man mit nur ein paar Zeilen Code schon nützliche Sachen bauen.

Es lohnt sich also immer mal wieder links und rechts zu schauen, auch wenn man meint, dass bestimmte Technologien, Tools, usw. für die momentane Arbeit uninteressant sind.

Schönen Dank an dieser Stelle noch einmal an die Veranstalter des .NET Open Space und an Alexander Groß für die spontane Bahn-Session.

 

Referenzen
[1] Dynamic in C# 4.0, Teil I: Einführung
http://www.codefest.at/post/2009/11/04/Dynamic-in-C-40-Teil-I-Einfuhrung.aspx


[2] Dynamic in C# 4.0: Introducing the ExpandoObject
http://blogs.msdn.com/b/csharpfaq/archive/2009/10/01/dynamic-in-c-4-0-introducing-the-expandoobject.aspx

[3] Creating and using Dynamic Objects
http://msdn.microsoft.com/en-us/library/ee461504.aspx

 

UPDATE: Ich bin gerade auf einen Artikel gestoßen, der so ziemlich genau das Gleiche macht und das Ganze noch um den Zugriff auf CSV-Dateien erweitert.
http://blogs.msdn.com/b/mcsuksoldev/archive/2010/02/04/dynamic-xml-reader-with-c-and-net-4-0.aspx

posted on 7/18/2011 8:54:35 AM (W. Europe Standard Time, UTC+01:00)  #