Dynamic in C# 4.0, Teil 2: MemberBinder, IndexBinder und InvokeMember

Im vorigen Posting habe ich versprochen, auf MemberBinder und IndexBinder in C# 4.0 einzugehen. Im letzten Beispiel haben wir bereits dynamisch Methoden hinzugefügt und verändert, doch dies geht noch wesentlich eleganter. Hierfür erstellen wir uns unser eigenes dynamisches Objekt. Dieses erbt von “DynamicObject”. In weiser Voraussicht auf das nächste Posting nennen wir das auch gleich mal “DynamicCSVObject”.

 public class DynamicCSVObject : DynamicObject

Damit wir unsere Member und die dazu passenden Variablen abspeichern können benötigen wir noch ein Dictionary.

 Dictionary<string, object> membervals;

Unser Konstruktor soll nun einen Array an Members und dazu passende Werte aufnehmen können. Wichtig ist hierbei, das die Anzahl der Members jenen der Werte entsprechen muss.

 public DynamicCSVObject(string[] members, object[] values)
{
    membervals = new Dictionary<string, object>();

    if (members.Length == values.Length)
    {
        for (int i = 0; i < members.Length; i++)
        {
            membervals.Add(members[i], values[i]);
        }
    }
}

Nun können wir uns auch bereits an die erste Methode machen. Konkret wollen wir einen Member zurück geben. Dies geschieht, indem man die Methode “TryGetMember” aufruft. Der Wert leitet sich hierbei aus dem Dictionary ab. Den Namen des Binders erhalten wir aus dem “GetMemberBinder”. Da das ganze ein “TryGet”-Pattern ist, wird True/False zurück geliefert und die Variablen in einer out-Variable ausgegeben. (Das TryGet-Pattern erlaubt eine sichere Abfrage von Werten und wurde im Buch von Krzysztof Cwalina und Brad Abrams beschrieben)

 public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    return membervals.TryGetValue(binder.Name, out result);
}

Nun können wir die Methode auch bereits Testen. Hierfür geben wir 2 Member und 2 Werte (jeweils vom Typ String der Einfachheit halber) an.

 dynamic dyn = new DynamicCSVObject(new string[] { "Date", "Value" }, new string[] { "12.12.2009", "22" });

Console.WriteLine(dyn.Date);

Nun ist es auch noch sehr interessant, wie man diese Members setzen kann. Ähnlich der TryGetMember-Methode gibt es nun eine TrySetMember-Methode, welche darauf wartet überschrieben zu werden. Diese Methode liefert zum einem den SetMemberBinder und zum anderen ein Objekt mit dem Wert. Wir überprüfen zu Beginn ob der Wert bereits vorhanden ist. Wenn nicht, wird dieser hinzugefügt.

 public override bool TrySetMember(SetMemberBinder binder, object value)
{
    if (!membervals.ContainsKey(binder.Name))
    {
        membervals.Add(binder.Name, value);
        return true;
    }
    else
    {
        return false;
    }
}

Nun können wir auch diese Methode testen.

 dyn.MyOwn = DateTime.Today;

 Console.WriteLine(dyn.MyOwn);

 Console.ReadLine();

image

Wie in dieser Abbildung erkennbar ist funktioniert das auch schon sehr gut.

Doch damit alleine wollen wir uns nun auch noch nicht zufrieden geben. Schließlich haben wir bis jetzt nur Properties abgefragt. Dies könnte man auch mit Dictionaries einfacher gestalten. Aber es ist auch Möglich, Methodenaufrufe zu machen. Hierfür gibt es die Methode “TryInvokeMember”. Diese bekommt zum einem den InvokeMemberBinder, dann einen Array von “args” (welche die Parameter darstellen) und das Ergebnis.

Wir wollen Methoden aufrufen, welche Text zu einem vorhandenen Property hinzufügen. Der Text soll hierbei aus den Übergabe Parametern kommen. Damit das funktioniert müssen wir folgendes überprüfen:

  1. Beginnt die Methode mit “Add”?
  2. Ist alles danach ein im Dictionary vorhandenes Property?

wenn das funktioniert, iterieren wir über die Übergabe Parameter und fügen Sie einen StringBuilder an. Dieser StringBuilder ist dann unser Ergebnis.

 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
    string member;
    object objmember;

    StringBuilder sb;

    if (binder.Name.StartsWith("Add"))
    {
        member = binder.Name.Substring(3);

        if (membervals.TryGetValue(member, out objmember))
        {
            sb = new StringBuilder(objmember.ToString());

            foreach (object o in args)
            {
                sb.Append(o.ToString());
            }

            result = sb.ToString();
            return true;
        }

        result = null;
        return false;
    }
    else
    {
        result = null;
        return false;
    }
}

Nun müssen wir das auch noch testen:

 Console.WriteLine(dyn.AddDate("1"));

Console.ReadLine();

Toll ist, das es wirklich mit beliebig vielen Parametern funktioniert:

 Console.WriteLine(dyn.AddValue("Mario", "\n", "Meir", "\n", "Huber"));

Console.ReadLine();

Wie man sieht es es nun bereits sehr dynamisch. Das Beispiel bietet nur einen kleinen Einblick in die Technologie. Hierbei könnte man anhand von unterschiedlichen Memberaufrufen DLL’s dynamisch nachladen, Webservice-Aufrufe vereinfachen wenn sich der Service öfter ändert oder eine Applikation schreiben welche sich an einen Kunden sehr einfach anpasst. Den Fantasien sind nun keine Grenzen mehr gesetzt ;)

Was nun auch noch möglich ist, ist ein Indexer. Hierbei wollen wir mit “obj[2]” auf ein Property zugreifen. Dies geschieht mithilfe der Methode “TryGetIndex”. im Array “indexes[0]” ist unser Index abgespeichert. Dies ist ebenfalls wieder vom TryGet-Design

 public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
    if ((int)indexes[0] <= membervals.Count)
    {
        result = membervals.ElementAt((int)indexes[0]);
        return true;
    }
    else
    {
        result = null;
        return false;
    }
}

Und natürlich wollen wir das auch noch ausführen.

 Console.WriteLine(dyn[2]);

Console.ReadLine();

image

Und fertig!

Im nächsten Posting werde ich die Klasse weiterverwenden und zeigen wie man dynamisch CSV-Daten parsen kann und diese in WPF mit Databinding verwendet.