⬆️ ⬇️

DynamicXml: a "dynamic" wrapper for working with XML data

I once wrote that, in spite of my love for static typing, in some scenarios the advantages of the freedom that dynamic typing gives may outweigh the disadvantages associated with it. Last time there was a discussion about Dynamic LINQ , and this time it will be about using the new C # 4.0 feature called dynamic to work with such initially weakly typed data like XML.



NOTE

The source code for the DynamicXml library discussed in this article is available on github



Introduction



Starting from version 4.0, C # added support for dynamic programming, thanks to a new static type called dynamic . Essentially, the use of this keyword tells the compiler to generate all the necessary code so that the binding process and dispatch operations are performed at runtime, instead of determining all these characteristics at compile time. At the same time, the compiler generates all the necessary code using the DLR library - Dynamic Language Runtime (*), which was originally created when designing the Iron Python programming language and later became part of the .Net Framework 4.0, as the basis for implementing dynamic programming languages, as well as for interactions between them.



')

Despite the appearance of the dynamic keyword, the C # programming language remained basically statically typed; you still need to explicitly indicate that the decision about what will happen to this code is delayed until runtime. In addition, no one assumes that this opportunity will be used daily; This function is primarily intended for interacting with other dynamically typed languages ​​such as Iron Python, Iron Ruby, as well as for interacting with a weakly typed environment such as VSTO (Visual Studio Tools for Office) and other COM APIs. Another classic example of using dynamic is creating “dynamic” wrappers above objects. A very well-known example is the creation of a shell to access private or protected members of a class (**); Another equally well-known example is the creation of a dynamic wrapper for accessing XML data. That's it on the implementation of the second possibility, we will stop here.



A simple example of reading XML data



So let's assume that we have a line that contains the following data (***):



< books >

< book >

< title > Mortal Engines </ title >

< author name ="" Philip Reeve "" />

</ book >

< book >

< title > The Talisman </ title >

< author name ="" Stephen King "" />

< author name ="" Peter Straub "" />

</ book >

</ books >




* This source code was highlighted with Source Code Highlighter .




And our task is to write a simple code that will read and process this data. Of course, in some cases it is more reasonable to deserialize all this stuff into some business logic object (in this case, a list of entities such as Book ) using the XmlSerializer class and manipulate this business object, however, in many cases, a more lightweight solution will do for example, based on LINQ 2 XML.



If we assume that the line above is contained in a variable named books, then you can use a very simple code to get the name to get some data:



var element = XElement .Parse(books);

string firstBooksTitle =

element.Element( "book" ).Element( "title" ).Value;

Assert.That(firstBooksTitle, Is.EqualTo( "Mortal Engines" ));



string firstBooksAuthor =

element.Element( "book" ).Element( "author" ). Attribute ( "name" ).Value;

Assert.That(firstBooksAuthor, Is.EqualTo( "Philip Reeve" ));



string secondBooksTitle =

element.Elements().ElementAt(1).Element( "title" ).Value;

Assert.That(secondBooksTitle, Is.EqualTo( "The Talisman" ));




* This source code was highlighted with Source Code Highlighter .




I have absolutely nothing against explicit use of XElement , moreover, this option is quite simple and elegant, but nevertheless this code is not without flaws. Firstly, it’s quite verbose, and secondly, it’s not entirely honest about error handling: if the books variable doesn’t have an element named book or an element named title, we get a NullReferenceException . So this code needs to be finalized with a file, which somewhat complicates its reading, understanding and maintenance.



// Dynamic Wrapper XElement

dynamic dynamicElement = // ...



//

string firstBooksTitle = dynamicElement.book.title;

Assert.That(firstBooksTitle, Is.EqualTo( "Mortal Engines" ));



// , ,

string firstBooksAuthor = dynamicElement.book.author[ "name" ];

Assert.That(firstBooksAuthor, Is.EqualTo( "Philip Reeve" ));



// , ,

string secondBooksTitle = dynamicElement.book[1].title;

Assert.That(secondBooksTitle, Is.EqualTo( "The Talisman" ));




* This source code was highlighted with Source Code Highlighter .




We still need to use an indexer to access attribute values, since we have to separate access to an element from access to an attribute, but since, as we will see later, everything is completely in our hands, we can make another decision and implement access to the attribute using another syntax. Nevertheless, the resulting syntax is simpler and more understandable than the code with direct use of LINQ 2 XML and we have to answer one simple question: what exactly should be hidden behind the comment “ we get Dynamic Wrapper over the XElement object ” in order for such street magic to be is possible.



Creating a "dynamic" shell for reading XML data





The easiest way to create a dynamic shell, which at the same time will have a fairly wide functionality, is to use the DynamicObject class from the System.Dynamic namespace. This class contains several virtual functions of the TryXXX type that allow you to "intercept" all basic actions with your dynamic object that will occur with it at run time, including method calls, accessing properties, type conversion, and many others.



Thus, all we need to do is create a class derived from DynamicObject , which would take an XElement object as a constructor parameter and override a number of helper methods:



/// <summary>

/// " " XElement

/// </summary>

public class DynamicXElementReader : DynamicObject

{

private readonly XElement element;



private DynamicXElementReader( XElement element)

{

this .element = element;

}



public static dynamic CreateInstance( XElement element)

{

Contract.Requires(element != null );

Contract.Ensures(Contract.Result< object >() != null );



return new DynamicXElementReader(element);

}

}




* This source code was highlighted with Source Code Highlighter .




The use of the factory method in this case is due to the fact that it more clearly shows the context of the use of this class; In addition to this method, the code of the DynamicXml library also contains a static class with extension methods that allow you to create instances of the dynamic shell more conveniently. The use of contracts (Code Contracts library) in this case merely simplifies the creation of such library classes, simplifies testing and documentation, and the static analyzer allows you to find errors during compilation. This is my personal preference, but if this approach seems unattractive to you (although it’s even in vain) using the magic search / replace tool, you can replace contracts with a convenient mechanism for checking input parameters.



Now let's go back to the implementation of the DynamicXElementReader class. First, a bit of theory: any reference to a property or a class method of a descendant from DynamicObject occurs in two steps: first, a search is made for the corresponding method or property with the same name in that same heir, and then the corresponding method is called, in which you can handle the absence of this member dynamically. Since no wrapper will ever provide absolutely all conceivable and inconceivable functionality (and in most cases this is not necessary), it is necessary to ensure that the underlying XElement is obtained from the wrapper. In addition, as we saw in the previous example, we need to make two indexers: one must take an int and return a sub-element, and the second must take a string (or, as we will see later on XName ) and return an attribute.



public class DynamicXElementReader : DynamicObject

{

/// <summary>

/// true, .

/// </summary>

/// <remarks>

/// Pure

/// </remarks>

[Pure]

public bool HasParent()

{

return element.Parent != null ;

}



public dynamic this [XName name]

{

get

{

Contract.Requires(name != null );



XAttribute attribute = element. Attribute (name);



if (attribute == null )

throw new InvalidOperationException(

"Attribute not found. Name: " + name.LocalName);



return attribute.AsDynamic();

}

}



public dynamic this [ int idx]

{

get

{



Contract.Requires(idx >= 0, "Index should be greater or equals to 0" );

Contract.Requires(idx == 0 || HasParent(),

"For non-zero index we should have parent element" );



//

if (idx == 0)

return this ;



// "" .

// ,

var parent = element.Parent;

Contract.Assume(parent != null );



XElement subElement = parent.Elements().ElementAt(idx);



// subElement null, ElementAt

// , .

// ,

// Contract.Assume

Contract.Assume(subElement != null );



return CreateInstance(subElement);

}

}



public XElement XElement { get { return element; } }



}




* This source code was highlighted with Source Code Highlighter .




The first indexer takes an XName as a parameter and is designed to get the attribute of the current element by its name. The return type is also dynamic, and the actual return value is obtained by calling the AsDynamic extension method on the XAttribute object. In principle, no one bothers to use the XAttribute type as the return type, but in this case, to obtain the immediate value of the attribute, you will have to additionally refer to the Value property, the resulting value, or use an explicit type conversion. In general, the implementation of a dynamic shell for attributes is much simpler, and implemented in a similar way.



Now let's move on to implementing the two main (for this class) virtual methods of the DynamicObject class: the TryGetMember method — which is responsible for accessing a property or field of the type dynamicObject.Member , as well as the TryConvert method — which is called during an implicit type conversion from a dynamic typed object to a static typed, string value = dynamicObject .



public class DynamicXElementReader : DynamicObject

{

// not used

private XElement element;

public static dynamic CreateInstance( XElement ) { return null ;}



/// <summary>

/// :

/// SomeType variable = dynamicElement;

/// </summary>

public override sealed bool TryConvert(ConvertBinder binder, out object result)

{

// XElement

// xml-

if (binder.ReturnType == typeof ( XElement ))

{

result = element;

return true ;

}



//

//

string underlyingValue = element.Value;

result = Convert .ChangeType(underlyingValue, binder.ReturnType,

CultureInfo.InvariantCulture);



return true ;

}



/// <summary>

///

/// </summary>

public override bool TryGetMember(GetMemberBinder binder, out object result)

{

string binderName = binder.Name;

Contract.Assume(binderName != null );



// ,

//

XElement subelement = element.Element(binderName);

if (subelement != null )

{

result = CreateInstance(subelement);

return true ;

}



// ,

//

return base .TryGetMember(binder, out result);

}



}




* This source code was highlighted with Source Code Highlighter .




As mentioned above, the TryConvert method is called whenever any attempt is made to convert an xml element or one of its sub-elements to the specified type. Since we can easily get the value of the current xml element, all that is needed to implement this method is to call the ChangeType of the Convert class; the only exception is the XElement type, which is processed separately and allows you to get the underlying XElement directly.



The TryGetMember method is also quite simple: first we get the name of the member that the user code is trying to access, and then we try to find an element with this name. If the specified element is found, we create a dynamic shell and return it via the output parameter result. Otherwise, we call the base version, which leads to the exception of the runtime, which will say that the requested member was not found.



All this allows you to use the shell as follows:



// bookXElement

string firstBooksElement = dynamicElement.book;

Console .WriteLine( "First books element: {0}" , firstBooksElement);



//

string firstBooksTitle = dynamicElement.book.title;

Console .WriteLine( "First books title: {0}" , firstBooksTitle);



// int

int firstBooksPageCount = dynamicElement.book.pages;

Console .WriteLine( "First books page count: {0}" , firstBooksPageCount);




* This source code was highlighted with Source Code Highlighter .




The result of the execution of this code:



First books element: < book >

< title > Mortal Engines </ title >

< author name ="Philip Reeve" />

< pages > 347 </ pages >

</ book >



First books title: Mortal Engines

First books page count: 347

First books author: Philip Reeve

Second books title: The Talisman




* This source code was highlighted with Source Code Highlighter .




Creating a "dynamic" shell to create / modify XML data



The reason for creating two classes, one responsible for reading the data, and the other for creating and modifying, is due to the fact that in the implementation of the TryGetMember method we cannot know in advance what the lower member is addressed to. After all, if this access occurs to read the data, and the specified element is not in the original XML data, then the most logical behavior is to generate an exception, which says that the element with the specified name was not found. This is exactly how the above implementation in the DynamicXElementReader class behaves . However, we need a completely different behavior when creating / changing XML data: in this case, instead of generating an exception, we need to create an empty element with the specified name; for it is quite logical to assume that there may not be (or rather, most likely there will not be) an element with the specified name in the created element.



Thus, to the above read-only DynamicXElementReader class, we add another one, DynamicXElementWriter , whose task is to create and modify XML data. However, since these two classes have a lot in common, for example, the implementation of the TryConvert method, as well as some auxiliary methods, such as HasParent, the actual code contains another auxiliary class DynamixXElementBase , which eliminates code duplication and simplifies the implementation of its descendants. However, since it is somewhat more difficult to analyze code with an additional base class, I will not show it here.



The main difference in the dynamic shell intended for creating / modifying XML data is the presence of setters for two indexers: one for changing the value of attributes, and the second for adding additional elements. The second difference is the presence of two additional non-dynamic methods: SetValue and SetAttributeValue , which serve to change the value of the current element and its attributes.



public class DynamicXElementWriter : DynamicObject

{

// ,



/// <summary>

///

/// </summary>

public void SetValue( object value )

{

Contract.Requires( value != null );



element.SetValue( value );

}



/// <summary>

///

/// </summary>

public void SetAttributeValue(XName name, object value )

{

Contract.Requires(name != null );

Contract.Requires( value != null );



element.SetAttributeValue(name, value );

}



/// <summary>

///

/// </summary>

public dynamic this [XName name]

{

get

{

//

}



set

{

//

// XElement.SetAttributeValue,

element.SetAttributeValue(name, value );

}



}



/// <summary>

/// ""

/// </summary>

public dynamic this [ int idx]

{

get

{

//

Contract.Requires(idx >= 0, "Index should be greater or equals to 0" );

Contract.Requires(idx == 0 || HasParent(),

"For non-zero index we should have parent element" );



//

if (idx == 0)

return this ;



// "" .

// ,

var parent = element.Parent;

Contract.Assume(parent != null );



// "" ,

//

XElement subElement = parent.Elements(element.Name).ElementAtOrDefault(idx);

if (subElement == null )

{

XElement sibling = parent.Elements(element.Name).First();

subElement = new XElement (sibling.Name);

parent.Add(subElement);

}



return CreateInstance(subElement);

}



set

{

Contract.Requires(idx >= 0, "Index should be greater or equals to 0" );

Contract.Requires(idx == 0 || HasParent(),

"For non-zero index we should have parent element" );



//

// ,

//

dynamic d = this [idx];

d.SetValue( value );

return ;

}



}

}




* This source code was highlighted with Source Code Highlighter .




The implementation of getters is very similar to the previous implementation, especially for the indexer that accepts XName and is designed to work with attributes. The implementation of an indexer that accepts an integer is somewhat more complicated, since even the getter contains additional logic for creating an additional “brother” if there is no such element yet. The implementation of the setters in both cases is rather trivial.



Another significant difference is the implementation of the TryGetMember method, as well as the presence of an additional TrySetMember method, which will be called if the xml value of the element is set: dynamicElement.SubElement = value .



/// <summary>

///

/// </summary>

public override bool TryGetMember(GetMemberBinder binder, out object result)

{

string binderName = binder.Name;

Contract.Assume(binderName != null );



//

XElement subelement = element.Element(binderName);



// ,

//

if (subelement == null )

{

subelement = new XElement (binderName);

element.Add(subelement);

}



result = CreateInstance(subelement);

return true ;

}



/// <summary>

///

/// </summary>

public override bool TrySetMember(SetMemberBinder binder, object value )

{

Contract.Assume(binder != null );

Contract.Assume(! string .IsNullOrEmpty(binder.Name));

Contract.Assume( value != null );



string binderName = binder.Name;



// ,

// , ;

// XElement.SetElementValue,

//

if (binderName == element.Name)

element.SetValue( value );

else

element.SetElementValue(binderName, value );

return true ;

}




* This source code was highlighted with Source Code Highlighter .




The main difference with the implementation of the TryGetValue method is that when accessing a sub element that is not in the original xml tree, instead of generating an exception, an element with the specified name will be added. The implementation of the TrySetMember method is also not too complicated due to the fact that the XElement method does for us all the wrong work. SetElementValue , which will be added by the element with the necessary name if necessary.



findings



I do not exclude at all that the above implementation contains errors or is not perfect in this or that matter. However, the main task of the article is to show the principle of creating dynamic shells around statically typed objects, as well as to show the benefits of dynamic programming in an initially statically typed programming language like C #. And although this implementation may be far from ideal, it is very well tested, and successfully participates in a couple of small projects. In addition, it is freely available on github , and each of you can be used by its ideas (as well as implementation) at its own discretion.



Once again, the source code of the DynamicXml library is available here .



- (*) The most interesting thing is that the DLR - Dynamic Language Runtime has no relation to the execution time, but is only a “normal” library that cunningly manipulates expression trees.



(**) There are several examples showing this feature, for example, here and here .



(***) This is a slightly modified example that John Skeat used in one of the examples for his book “C # In Depth”, 2nd edition.

Source: https://habr.com/ru/post/119036/



All Articles