But there were some obvious flaws in this example: While ExpandoObject always better syntax, LINQ to XML which provides a wealth of useful collection methods that helped her work with XML files. Therefore, it is possible to combine these two advantages, to get a better syntax and still get all these methods? The answer is yes, but you need other namespace Technique.Dynamic: DynamicObject.
In the previous post I showed how you can use the new feature and class dynamics ExpandoObject add and remove properties at runtime, and how this can make the code more readable and more flexible than code written with the syntax of LINQ to XML .
The class lets you override DynamicObject get or set operations as a member, calling a system, or perform any binary, unary, or type conversion operation. To illustrate the issue, let's create a simple object that replaces the "obtaining property" operation, so that each time you access a property that returns the property name as a string. This example has no practical value.
- public class SampleObject : DynamicObject
- {
- public override bool TryGetMember(
- GetMemberBinder binder, out object result)
- {
- result = binder.Name;
- return true;
- }
- }
As with ExpandoObject, we must use the dynamic keyword to create an instance of this class.
- dynamic obj = new SampleObject();
- Console.WriteLine(obj.SampleProperty);
- //Prints "SampleProperty".
-
- Now lets move to a more complex example and create a wrapper for the XElement object. Once again, Ill try to provide better syntax for the following LINQ to XML sample.
- XElement contactXML =
- new XElement("Contact",
- new XElement("Name", "Patrick Hines"),
- new XElement("Phone", "206-555-0144"),
- new XElement("Address",
- new XElement("Street1", "123 Main St"),
- new XElement("City", "Mercer Island"),
- new XElement("State", "WA"),
- new XElement("Postal", "68042")
- )
- );
First of all, I require to generate an analog of ExpandoObject. I still require to be able to dynamically add & remove properties. But since I am essentially generating a wrapper for the XElement type, I’ll use XElement in lieu of the dictionary to maintain the properties.
-
- public class DynamicXMLNode : DynamicObject
- {
- XElement node;
- public DynamicXMLNode(XElement node)
- {
- this.node = node;
- }
- public DynamicXMLNode()
- {
- }
- public DynamicXMLNode(String name)
- {
- node = new XElement(name);
- }
- public override bool TrySetMember(
- SetMemberBinder binder, object value)
- {
- XElement setNode = node.Element(binder.Name);
- if (setNode != null)
- setNode.SetValue(value);
- else
- {
- if (value.GetType() == typeof(DynamicXMLNode))
- node.Add(new XElement(binder.Name));
- else
- node.Add(new XElement(binder.Name, value));
- }
- return true;
- }
- public override bool TryGetMember(
- GetMemberBinder binder, out object result)
- {
- XElement getNode = node.Element(binder.Name);
- if (getNode != null)
- {
- result = new DynamicXMLNode(getNode);
- return true;
- }
- else
- {
- result = null;
- return false;
- }
- }
- }
And here is how you can use this class.
- dynamic contact = new DynamicXMLNode("Contacts");
- contact.Name = "Patrick Hines";
- contact.Phone = "206-555-0144";
- contact.Address = new DynamicXMLNode();
- contact.Address.Street = "123 Main St";
- contact.Address.City = "Mercer Island";
- contact.Address.State = "WA";
- contact.Address.Postal = "68402";
The next interesting line is contact.Address = new DynamicXMLNode(). Fundamentally, in this particular example this line means that I require to generate a node that has subnodes. For this property, the TrySetMember technique creates an XElement without a value.
Let’s look at the contact object. When this object is created, it initializes its inner XElement. If you set a property value, like in contact.Phone = "206-555-0144", the TrySetMember technique checks whether an element with the name Phone exists in its XElement. If it does not exist, the technique creates the element.
The most difficult here is a line like contact.Address.State = "WA". First, the technique is called TryGetMember for contact.Address and returns a new DynamicXMLNode, which is initialized by the XElement with the name address. (In theory, could have returned the XElement itself, but that would make the example more complicated.) Technique then called TrySetMember. The technique looks for the element contact.Address State. If you do not find six who creates it.
String state = contact.Address.State;
I have several options here. I can change the TryGetMember method to return actual values for leaf nodes, for example. But let’s explore another option: override type conversion. add the following method to the DynamicXMLNode class.
- public override bool TryConvert(
- ConvertBinder binder, out object result)
- {
- if (binder.Type == typeof(String))
- {
- result = node.Value;
- return true;
- }
- else
- {
- result = null;
- return false;
- }
- }
The last thing I’m going to show is how to get access to the XElement methods. Let’s override the TryInvokeMember method so that it will redirect all the method calls to its XElement object. Of coursework, I’m using the Technique.Reflection namespace here.
Now whenever I have an explicit or implicit type conversion of the DynamicXMLNode type, the TryConvert method is called. The method checks what type the object is converted to &, if this type is String, the method returns the value of the inner XElement. Otherwise, it returns false, which means that the language should choose what to do next (in most cases it means that you’re going to receive a run-time exception).
- public override bool TryInvokeMember(
- InvokeMemberBinder binder,
- object[] args,
- out object result)
- {
- Type xmlType = typeof(XElement);
- try
- {
- result = xmlType.InvokeMember(
- binder.Name,
- BindingFlags.InvokeMethod |
- BindingFlags.Public |
- BindingFlags.Instance,
- null, node, args);
- return true;
- }
- catch
- {
- result = null;
- return false;
- }
- }
This process allows you to call methods on any node XElement object DynamicXMLNode. The most obvious disadvantage is the lack of IntelliSense.