Click here to Skip to main content
15,891,248 members
Articles / Programming Languages / XML

Creating Your Own .NET DynamicObject. Why, When and How

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
26 Oct 2020Apache6 min read 3.9K   7  
How, why, when and how to create your own DynamicObject in .NET
In this post, I will describe what is the dynamic type in C# and its pros and cons. I will also show how to write a class defining a new type of dynamic object that allows accessing XML data with a simplified syntax. This is similar to the feature that JSON.NET provides for querying JSON data.

Spoilers

This post is the first in a short series investigating somewhat exotic parts of .NET.

During the series, I will be writing the main components of a template-based image generator. I will cover:

The complete source code for the project is available on GitHub. The repo contains a fully working generator of card images, which I named Card Artist. Board game designers can use it to create their own cards for playtesting or for professional printing. The GitHub repo also contains binaries for the application as well as documentation on how to create your own card templates.

What Is Dynamic

In 2010, Microsoft released the Dynamic Language Runtime (DLR) as part of version 4 of the .NET Framework. The DLR allowed supporting dynamic languages, like Python, on top of the .NET CLR. At that time, the dynamic type was also added to C# allowing interoperability with dynamic languages.

A variable of type dynamic is similar to a variable of type object:

  • It doesn’t hold any information about the type of the object it references.
  • It can hold a reference to any type of object.

Differently from object, a variable of type dynamic can be used to invoke the methods and access the properties and fields of its type without casting:

C#
class Class1
{
  public int ReturnValue(int a, int b) => a + b;
  public int A = 5;
}

var typedReference = new Class1();
var a2 = typedReference.A;
var b2 = typedReference.ReturnValue(1, 2);

//Now with object
object objectReference = new Class1();
var a2 = ((Class1)objectReference).A;
var b2 = ((Class1)objectReference).ReturnValue(1, 2);

//Now with dynamic
dynamic dynamicReference = new Class1();
int a3 = dynamicReference.A;
int b3 = dynamicReference.ReturnValue(1, 2);

//This compiles successfully and fails at runtime
dynamicReference.ThisFunctionDoesntExist();

Notice that we need to specify the type of a3 and b3 because the compiler doesn’t have information about the type of the field A and the return type of the method ReturnValue. The compiler doesn’t even know whether such field and method exist as demonstrated by the fact that it doesn’t complain about us calling ThisFunctionDoesntExist(). If we hadn’t specified a type for a3 and b3 (and used var instead), they would also be variables of type dynamic. At any time, we can cast a variable of type dynamic to its actual type.

I personally hate dynamic languages with passion: why would you give up the advantage of having the compiler finding bugs for you just to avoid writing a few more lines of code? For this reason, it is very rare to see dynamic used in C#.

Using dynamic makes a lot more sense though when you don’t have access to a type at compile time. Usually, you would use reflection:

C#
var type = Assembly.GetExecutingAssembly().GetType("MyNamespace.Class1");
var referenceToUnknowType = type
  .GetConstructor(Array.Empty<Type>())
  .Invoke(Array.Empty<object>());
var a4 = (int)t
  .GetField("A")
  .GetValue(c);
var b4 = (int)t
  .GetMethod("ReturnValue", new Type[] { typeof(int), typeof(int) })
  .Invoke(c, new object[] { 1, 2 });

This is NOT pretty!

In case you are wondering why you would ever need something like this, it is pretty common to use reflection when writing applications that support plug-ins. Because plug-ins are written separately from your application, you cannot use the plug-in's types when compiling the application itself.
Another use case would be accessing private methods and members of an object (ugh!).

Dynamic allows to write the same code in a much more readable way:

C#
var type = Assembly.GetExecutingAssembly().GetType("MyNamespace.Class1");
var dynamicReferenceToUnknownType = type
  .GetConstructor(Array.Empty<Type>())
  .Invoke(Array.Empty<object>());
int a4 = dynamicReferenceToUnknownType.A;
int b4 = dynamicReferenceToUnknownType.ReturnValue(1, 2);

This is as safe as using reflection, because reflection also doesn’t provide any compile-time verification, but it is much more readable.

It is worth noting that the reflection code is hundreds of times slower than the “normal” code (the one above using typedReference). Dynamic is also notorious for being slow: in this case, it is slightly faster than using reflection, but still hundreds of times slower than the statically typed alternative.

The dynamic type can also be used to easily access other data that is not statically typed like COM objects (some information about this here) or JSON files.

Let’s Create Our Own DynamicObject

As we discussed before, the syntax of dynamic objects is really simple and clean but using them is really slow and error-prone. The slowness is because the DLR has to search every time for the object’s “members” using their name, the danger comes from the possibility of the member not being found.

For this reason, dynamic objects are ideal to expose operations that have the same “slow and risky” characteristics. Parsing an XML file is one of such operations:

  • It is slow because the XML language has to be parsed and the elements and attributes names have to be matched to our query,
  • It is risky because the C# compiler doesn’t guarantee that the XML file adheres to the expected schema (the file is not even part of the application, so what could the compiler do?).

Additionally, dynamic objects don’t have any intellisense support. But we are never getting any intellisense support for traversing the XML data structure, no matter what technology we use, so nothing is lost there either.

Usually, in order to access XML data in C#, we need to write something like this:

C#
var xml = @"<Characters>
  <Batman Age=""81"">
    <Equipment>
      <Item>Batarangs</Item>
      <Item>Shark repellent</Item>
    </Equipment>
  </Batman>
  <Robin Age=""37"">
    <Equipment>
      <Item>Red hood</Item>
    </Equipment>
  </Robin>
</Characters>";

var characters = XDocument.Parse(xml).Root;
var batmanAge = int.Parse(characters.Element("Batman").Attribute("Age").Value);
var batarangs = characters.Element("Batman").Element("Equipment")
  .Elements().First().Value;

By exposing XML data as a dynamic object, we can achieve a much more readable syntax. This can be done by creating a new class and extending DynamicObject.

C#
class XmlDynamicElement : DynamicObject
{
  private readonly XElement Element;

  public XmlDynamicElement(XElement element)
  {
    Element = element;
  }
  ...
}

dynamic characters = new XmlDynamicElement(XDocument.Parse(xml).Root);
int batmanAge = characters["Batman", 0].Age;
string batarangs = characters["Batman", 0]["Equipment", 0][0];

Image 3

The Dynamic Duo:
XML and DynamicObject.

Image by ErikaWittlieb, used under PixaBay license

First of all, our new XmlDynamicElement class should support access to children elements using the [] operator. Element[int] will return the n-th children. Element[string, int] will return the n-th element with the specified name. This is actually pretty simple to implement:

C#
class XmlDynamicElement : DynamicObject
{
  public override bool TryGetIndex(GetIndexBinder binder, object[] indexes,
                                   out object result)
  {
    result = null;
    XElement childElement;
    if (indexes.Length == 1 &&
        indexes[0] is int index)
    {
      childElement = Element.Elements().ElementAtOrDefault(index);
    }
    else if (indexes.Length == 1 &&
             indexes[0] is string name)
    {
      result = Element.Elements(name).Select(e => new XmlDynamicElement(e))
        .ToArray();
      return true;
    }
    else if (indexes.Length == 2 &&
             indexes[0] is string name2 &&
             indexes[1] is int index2)
    {
      childElement = Element.Elements(name2).ElementAtOrDefault(index2);
    }
    else
      throw new ArgumentException("Invalid index type");

    if (childElement == null)
      throw new IndexOutOfRangeException();
    result = new XmlDynamicElement(childElement);
    return true;
  }
...

In the code above, I also added support for Element[string], returning an array containing the elements with the specified name. I didn’t return an IEnumerable because extension methods don’t work well with dynamic. I don’t really need to handle exceptions which will just surface to the user if they use invalid indexes.

Next, we want an XmlDynamicElement to be automatically convertible to a string or a number. It would also be nice to be able to get back the XElement in case we want to.

C#
class XmlDynamicElement : DynamicObject
{
  public override bool TryConvert(ConvertBinder binder, out object result)
  {
    if (binder.Type == typeof(String))
      result = ToString();
    else if (binder.Type == typeof(XElement))
      result = Element;
    else
      result = Convert.ChangeType(ToString(), binder.Type,
        CultureInfo.InvariantCulture);
    return true;
  }

  public override string ToString() =>
    Element.Nodes().Aggregate(new StringBuilder(),
      (sb, n) => sb.Append(n.ToString())).ToString();
...

In order to be coherent, I defined the conversion to string to return the same value as the .ToString() method which provides the concatenated content of the element. I also leveraged Convert.ChangeType to easily support the default conversion to multiple types.

The final bit of implementation for XmlDynamicElement is allowing access to attributes. Because an XML element cannot have two attributes with the same name, we can represent attributes as properties:

C#
class XmlDynamicElement : DynamicObject
{
  public override bool TryGetMember(GetMemberBinder binder, out object result)
  {
    var attribute = Element.Attribute(binder.Name);
    result = attribute != null ? new XmlDynamicAttribute(attribute) : null;
    return attribute != null;
  }
  
  public override IEnumerable<string> GetDynamicMemberNames() =>
    Element.Attributes().Select(a => a.Name.ToString());
...

The attributes are then represented by a very similar DynamicObject:

C#
class XmlDynamicAttribute : DynamicObject
{
  private readonly XAttribute Attribute;
  
  public XmlDynamicAttribute(XAttribute attribute)
  {
    Attribute = attribute;
  }
  
  public override bool TryConvert(ConvertBinder binder, out object result)
  {
    if (binder.Type == typeof(String))
      result = ToString();
    else if (binder.Type == typeof(XAttribute))
      result = Attribute;
    else    
      result = Convert.ChangeType(ToString(), binder.Type,
        CultureInfo.InvariantCulture);
    return true;
  }
  
  public override string ToString() =>
    return Attribute.Value;
}

We may want to add some extra functionalities to our classes as methods, for example, it could be useful to access the name of an element or all its children.

C#
class XmlDynamicElement : DynamicObject
{
  public string Xml() =>
    Element.ToString();
  
  public XmlDynamicElement[] Elements() =>
    Element.Elements().Select(e => new XmlDynamicElement(e))
      .ToArray();
  
  public XmlDynamicAttribute[] Attributes() =>
    Element.Attributes().Select(a => new XmlDynamicAttribute(a))
      .ToArray();
...

Unfortunately, these methods are not accessible when the XmlDynamicElement object is behind a dynamic reference. To address this, we need to implement TryInvokeMember:

C#
class XmlDynamicElement : DynamicObject
{
  public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args,
                                       out object result)
  {
    if (args == null || args.Length == 0)
    {
      switch (binder.Name)
      {
        case "Xml":
          result = Xml();
          return true;
        case "Elements":
          result = Elements();
          return true;
        case "Attributes":
          result = Attributes();
          return true;
      }
    }
    result = null;
    return false;
  }
...

Wrapping It Up

There is a pretty hefty performance impact from this approach. Based on some superficial testing, I can see that this implementation is roughly 20 times slower than using XElement and XAttribute directly. This is a big price to pay for simplified syntax!

There are times though when usability is paramount. This will be exactly the case in a couple of posts when we will use XmlDynamicElement to allow users to reference XML data from a templated XAML document. Because we will ask the users to write XML queries within a weird mix of C# and XAML (look up Razor if you can’t wait), we really want to keep the added complexity as low as possible.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Software Developer (Senior) Microsoft
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --