With C# 4.0 a new keyword will be introduced called "dynamic". Objects of type "dynamic" can be called with any signature. The Dynamic Language Runtime (DLR) in the background converts the calls into valid calls on the target object. E. g. instead of calling xmlElement.GetAttributeNode("Bla") you could call xmlElement.Bla to retrieve the value of the attribute. This way the concepts of dynamic programming enter the world of statically typed C#.
This technique is well-known in the scripting area for a long time (even Smalltalk had such a feature). IronRuby is the Ruby-Implementation on the .NET-CLR so I wanted to experiment a little with its dynamic capabilities.
Let’s start with a small XML document containing data of some addresses:
addressbook = <<addresses
<addressbook>
<address>
<name>John Doe</name>
<street>Somestreet 1</street>
<city>Somecity</city>
<phonenumbers>
<phone kind="home">91829182</phone>
<phone kind="mobile">7271827</phone>
</phonenumbers>
</address>
<address>
<name>Jane Doe</name>
<street>Doestreet 3</street>
<city>Somecity</city>
</address>
</addressbook>
addresses
Instead of handling XmlElements, XmlAttributes and all that stuff I want to execute this Ruby code:
doc = XmlDocument.new
doc.load_xml addressbook
doc.addressbook.each_address { |adr|
puts "%s lives in %s, %s" % [adr.name, adr.street, adr.city]
if adr.phonenumbers
adr.phonenumbers.each_phone { |p|
puts " Phonenumber %s (%s)" % [p.text, p[:kind]]
}
end
}
Quite easy looking, isn’t it ? But how is this possible ? The XmlDocument-Type doesn’t have a member called “addressbook”. Well, here begins the magic of dynamic programming. What happens when Ruby encounters a method that doesn’t exist ? Right, it calls the method_missing-Method which, when not overridden, raises the wellknown exception. Let’s overwrite the method instead by just specifying another XmlDocument class:
class XmlDocument
def method_missing(method)
if document_element.name == method.to_s
document_element
end
end
end
We don’t replace the XmlDocument class here, we just add a little bit of code for our needs: we just check if the name provided by the missing method equals the name of the Root-Element and return this. When I run this code
doc = XmlDocument.new
doc.load_xml addressbook
puts doc.addressbook
I get the output System::Xml::XmlElement which is actually the type of the document_element-Member. Works, great.
OK, that was easy, but what about these each-Methods and the other accessors. This is a little bit more difficult. Here is the complete code of the XmlElement-Class:
class XmlElement
alias_method :the_old_name, :name
def method_missing(method, *args, &block)
each_index = method.to_s.index("each_")
if each_index == 0
examine_each(method, &block)
else
examine_child(method)
end
end
def [](attr_name)
attr = get_attribute_node(attr_name)
return attr.value if attr
end
def name
child = examine_child(:name)
return child ? child : the_old_name
end
def text
inner_text
end
private
def examine_each(each_method, &block)
child_name = each_method.to_s[5..-1]
select_nodes(child_name).each { |child|
block.call child
}
end
def examine_child(child_name)
child = select_single_node(child_name)
if child.class == XmlElement and
not child.child_nodes.any? { |c| c.class != XmlText }
child.inner_text
else
child
end
end
end
Some explanations:
- By using alias_method I can remember the original implementation of the replaced “name”-Member
- As you can see the method_missing-Method doesn’t only get a method name but also the args and the code block that was provided. Therefore I can forward the block to the examine_each-Method which yields the block for each matching child
- The string “each_” is used to identify if the user wants to iterate over child nodes or access the child directly.
- The []-Operator is replaced to read attribute values
- The examine_child-Method uses the assumption that a node that doesn’t have any child nodes with an XmlText can be considered as Text-Nodes.
When I run the program from above I’ll get the desired output:
John Doe lives in Somestreet 1, Somecity
Phonenumber 91829182 (home)
Phonenumber 7271827 (mobile)
Jane Doe lives in Doestreet 3, Somecity
I think this small example shows the power of dynamic programming. I’m curious if C# will get the same treatment as IronRuby and have a concept like the method_missing-Callback.