Fun With Dynamic Objects (Doug Rothaus)
while back, I remember being asked if there was a simple way to expose a source XML document as an object with properties. That is, if the root XML element had a child element <Name>Doug</Name>, then the object would have a Name property that was a string and returned “Doug”. The catch was that the XML document did not conform to a specific schema. Hence, you could not simply create an object with a Name property, because you did not know if the source document had a <Name> element. While there are ways to expose the XML data dynamically, you couldn’t quite do what was being asked.
Enter dynamic objects in Visual Basic 2010. Now, this is possible. This means that you can use the classes in the System.Dynamic namespace to create objects that expose properties and methods dynamically at run-time and solve the original problem. In this example, we will create an object that inherits the System.Dynamic.DynamicObject class. The DynamicObject class has methods that you can override to provide code specific to your implementation. When VB performs a late-bound request on an object that implements the DynamicObject class, it calls one of the methods of the DynamicObject class to obtain the result. Consider this simple example:
Public Class SimpleObject
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
If binder.Name = “Address” Then
result = “Value”
Now consider this code that creates an instance of the object and accesses properties of that instance.
Dim t As Object = New SimpleObject()
First, in order to ensure that requests made of the dynamic object are late-bound, we need to type the variable as Object. Then we make two late-bound requests for object properties: Address and Street. When you access the Address property, a call is made to the TryGetMember method of the DynamicObject class, which we have overridden. The GetMemberBinder object instance that is passed to our TryGetMember method contains the name of the requested member in its Name property. Our small sample matches that name and returns the string “Value”. When you access the Street property, a call is made to the TryGetMember method, the name does not match anything that our code supports, so the method returns False. False indicates an unsupported member, and an exception is thrown.
To solve our initial problem of “hiding” source XML and exposing an object instead, we can use the DynamicObject class in this same fashion. When a property is requested of our dynamic object, our code can search the XML source for a matching XML element name, and return the corresponding value as the property value. Let’s look at an example.
Let’s jump into the code and we’ll talk about how the object behaves as we go. We start with a class that inherits DynamicObject. Let’s name it DynamicXmlObject.
Public Class DynamicXmlObject
We will add two constructors for the class. One that takes a path to an XML file as the source, and another that takes an XElement. The constructor that takes an XElement is not just for user convenience. We’ll use this later when dealing with child elements that have attributes or children. Also for this class, we won’t expose a settable property for the source XML, so we’ll “disable” the empty constructor by making it Protected.
Public Class DynamicXmlObject
‘ The source XML for this instance.
Private p_Xml As XElement
‘ Create a new DynamicXmlObject with the specified source file.
Public Sub New(ByVal filePath As String)
If Not File.Exists(filePath) Then
Throw New Exception(“File does not exist.”)
p_Xml = XElement.Load(filePath)
‘ Create a new DynamicXmlObject with the specified source XElement.
Public Sub New(ByVal xml As XElement)
p_Xml = xml
‘ Disable the empty constructor.
Protected Sub New()
This next piece of code is optional. I’ve added it so that our object supports reflection-like functionality. You can expose the member names of your dynamic object using the GetDynamicMemberNames method of the DynamicObject class. Our sample object will return all of the child element names and attribute names of the current element. I’ve added a case-insensitive Distinct comparison so that only a single name is returned if multiple entries are found. The search looks at only the LocalName property of the XName for an element or attribute. XML namespaces are ignored. That is, <a:Name> and <b:Name> are considered to be duplicate element names.
Public Overrides Function GetDynamicMemberNames() As IEnumerable(Of String)
Dim names = (From e In p_Xml.Elements() Select e.Name.LocalName).Union(
From a In p_Xml.Attributes() Select a.Name.LocalName)
Return (From n In names