A Nice Use of C# Dynamic

Dynamic capabilities were added to C# 4.0. These capabilities provide the ability to intercept method and property invocations. In certain situations, this is extremely useful, as seen in SignalR.

I’m currently working on an XMPP library which parses a received XMPP Stanza into a class named BvElement. Abstracting from BvElement are classes such as IQ, Message, and Presence.

In a non-dynamic world I have to create properties to represent XML child nodes, or a method which accepts a name and performs the appropriate lookup. This leads to a proliferation of properties, and in most cases this is completely unnecessary – if the node is not present or empty, the consumer will handle that, I just need to return a BvElement representing the node. Where default behavior is expected, for example get is the default type of an IQ stanza, I need a property. But for most purposes, this is not necessary, especially since XMPP is extensible by design.

So instead of writing zillions of properties, using dynamic I am able to achieve code like this, which sets the namespace attribute from a Query element of an IQ:

iq.Query[Xl.Namespace] = Xl.RegisterNamespace;

If Query doesn’t exist, an empty element named Query is created and returned. If the Namespace attribute doesn’t exist, an empty attribute named Namespace is created. This greatly simplifies client-side code, there is no need for null testing. It’s pretty much the Null Object Pattern at work.

A few more examples, in this case there are no specialized classes like IQ etc.

// to create the following presence stanza

<presence from='juliet@example.com/chamber' id='pres1'>
  <show>dnd</show>
  <status>busy!</status>
</presence>

// we can do this

dynamic presence = new BvElement("presence");
presence["from"] = "juliet@example.com/chamber";
presence["id"]   = "pres1";
presence.Show    = "dnd";
presence.Status  = "busy!";

// to create the following message stanza

<message type="chat" from="sender@example.com" to="test@example.com">
    <body>Example message 1</body>
</message>

// we can do this

dynamic msg = new BvElement("message");
msg["type"] = "chat"
msg["from"] = "sender@example.com";
msg["to"]   = "test@example.com");
msg.Body    = "Example Message 1";

// to create the following example iq stanza

<iq type="set" id="1001">
  <query xmlns="jabber:iq:private" action="create" strict="true">
    <exodus xmlns="exodus:prefs">
      <defaultnick>Hamlet</defaultnick>
    </exodus>
  </query>
</iq>

// we can do this

dynamic iq = new BvElement("iq");
iq["type"] = "set";
iq["id"]   = "1001";

var q = iq.Query;
    q["xmlns"]  = "jabber:iq:private";
    q["action"] = "create";
    q["strict"] = "true";

    var e = q.Exodus;
        e["xmlns"]    = "exodus:prefs";
        e.DefaultNick = "Hamlet";

Of course, it would make sense to inherit specific types of XMPP Stanzas from BvElement, such as Message, Iq, Presence. Doing so would allow even simpler code through usage of constructor arguments for attributes, and sensible defaults. A Fluent API would help to. However, the above code is an example of the flexibility of dynamic where iq, presence and message don’t exist.

Dynamic Object

So how does it work? Firstly, I need to inherit from DynamicObject.

public class BvElement : DynamicObject, IEnumerable

Try Get Member

Then I implement the TryGetMember method, which is invoked if a method or property does not exist. In this method I look for the requested element, if it is present I return it, otherwise I add the required element before returning it.

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    var name = binder.Name;
    var i = IndexOf(name);
    result = i == -1 ? Add(name) : _elements[i];
    return true;
}

Try Set Member

Lastly, I implement the TrySetMember method. Here I am setting the value of an element, creating the element if it doesn’t exist.

public override bool TrySetMember(SetMemberBinder binder, object value)
{
    var name = binder.Name;
    var i = IndexOf(name);

    if (i == -1)
    {
        if (value is BvElement)
        {
            Add(value as BvElement);
        }
        else
        {
            Add(name, Convert.ToString(value));
        }
    }
    else
    {
        if (value is BvElement)
        {
            _elements[i] = (value as BvElement);
        }
        else
        {
            _elements[i].Value = Convert.ToString(value);
        }
    }

    return true;
}

Dynamic

The last piece of the puzzle is the dynamic keyword. You must use this type in order to access the dynamic features:

dynamic iq = myXmppPacket;

Now you can use it as mentioned above, i.e. iq.Query[Xl.Namespace]…

Summary

Dynamic is an excellent addition to C#, and surprisingly it performs much faster than reflection. It’s another paradigm for solving problems.

I hope this post helps.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s