Get to know DynamicObject

    Each time you have a new interesting feature in the language, people always appear who begin to squeeze the maximum out of the feature. DynamicObject- this is just such a feature that seems simple and understandable, but in playful pens it becomes a more dangerous undertaking.

    Acquaintance


    To begin with, let's see what kind of class it is System.Dynamic.DynamicObject. This class seems ordinary - for example, one can inherit from it and overload one or more of its methods. Only here are some difficult methods ... let's take a closer look.

    First, make a test object DOand inherit from DynamicObject:

    class DO : DynamicObject {}

    Now, using the keyword, dynamicwe can invoke some method on this object without any remorse:

    dynamic dobj = new DO();
    dobj.NonExistentMethod();

    Guess what we get. We RuntimeBinderExceptionget something called and this message.

    'DynamicObjectAbuse.DO' does not contain a definition for 'NonExistentMethod'

    which is natural, because NonExistentMethod()our class simply doesn’t have a method . But the interesting thing is that it can never be. This is all salt DynamicObject- the ability to call properties and methods that the class does not have . Either not at the time of compilation, or not at all.

    Saga of non-existent methods


    How did this happen? Very simple - when we call a method, we actually call a method

    bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

    In the case of a method call NonExistentMethod(), this method is called without arguments, and the parameter binderjust contains information about the call.

    {Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder}
        [Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder]: {Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder}
        base {System.Dynamic.DynamicMetaObjectBinder}: {Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder}
        CallInfo: {System.Dynamic.CallInfo}
        IgnoreCase: false
        Name: "NonExistentMethod"
        ReturnType: {Name = "Object" FullName = "System.Object"}

    In this case, we get the name of the method, which can be processed somehow. How - it's up to you. There can be any business logic. Again, there are mechanisms for getting arguments ( args) and returning results ( result). The method returns trueif everything went well or falseif everything went wrong. The return falsefrom this method will just raise the exception that we saw above.

    What are there other than methods?


    A set of overloaded operations for DynamicObjectimpressive. This is primarily the ability to respond to access to properties that are not present, as well as to conversions, unary and binary operators, access via an index, etc. Some operations are not intended for C # / VB at all - for example, intercepting the creation of an object, deleting members of an object, deleting an object by index, etc.

    There is one small incident - through thisyou will receive a static object DOinstead of a statically typed dynamic one DO. The solution to this problem is predictable:

    private dynamic This { get { return this; } }

    This is in case you really need it. Of course, you should not do stupid things like calling methods on Thisfrom TryInvokeMember()since you get corny StackOverflowException.

    Expandoobject


    ExpandoObject- This is generally a swan song. This class allows users to arbitrarily add methods and properties:

    dynamic eo = new ExpandoObject();
    eo.Name = "Dmitri";
    eo.Age = 25;
    eo.Print = new Action(() =>
      Console.WriteLine("{0} is {1} years old",
      eo.Name, eo.Age));
    eo.Print();

    Serializing such an object is certainly not an easy task because it implements IDictionary- an interface that is not serialized, for example, in XML due to some very muddy reasons related to the fragmentation of assemblies and interfaces. It does not matter. If you really need to, you can use System.Runtime.Serialization.DataContractSerializer:

    var s = new DataContractSerializer(typeof (IDictionary));
    var sb = new StringBuilder();
    using (var xw = XmlWriter.Create(sb))
    {
      s.WriteObject(xw, eo);
    }
    Console.WriteLine(sb.ToString());

    Naturally, such a thing will not serialize methods. To do this, you can organize dances with a tambourine around DataContractResolver, but the purpose of this article is not such practices.

    What to do about it?


    OK, in general, the functionality is understandable from the point of view of COM development, in which each more or less serious interaction is similar to clearing the Augean stables. Interaction with dynamic languages ​​is also a good plus, and if I were at least somewhat interested in this, I would definitely tell in this article about those binders and other infrastructural charms to which all this relates.

    Here is a great example that is quoted wherever possible (so hopefully this is not plagiarism). The bottom line is that when working with XML, access to elements and attributes XElementlooks simply inhuman:

    var xe = XElement.Parse(something);
    var name = xe.Elements("People").Element("Dmitri").Attributes("Name").Value; // WTF?

    This is just inhuman syntax. Here is a much more “glamorous" solution: first, we make a solution DynamicObjectthat resolves the contents with its virtual properties XElement:

    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;
        }
      }
    }

    Now the thing is small - if you need a property, you can simply take it through a point:

    var xe = XElement.Parse(something);
    var dxn = new DynamicXmlNode(xe);
    var name = dxn.people.dmitri.name;

    Monads and AOP


    Once again, I want to note that having the ability to control access to objects like this, we can attach an AOP in the style of Unity.Interceptor or another IoC + AOP framework that works on dynamic proxies to them. For example, in the example just above, we can guarantee that we will never be thrown away NullReferenceException, even if one of the elements in the chain is valid null. To do this, you really have to make fake objects, but this is akin to creating intermediate classes for fluent interfaces.

    DSL'ki


    Of course, the ability to “write anything” in classes brings us to the idea that, in principle, it is possible to build DSLs on this basis, which can not be checked statically (unlike the syntax in the MPS style), but can be used to Describe some tricky domain languages.

    “Stop,” you say, “but isn’t it easier to use strings, generators, and other meta-infrastructure?” In fact, it all depends on how you look at it. For example, our example with DynamicXmlNodethis is the DSL for which XML is a domain. Similarly, I can for example write the following:

    myobject.InvokeEachMethodThatBeginsWithTest()

    The moral is that in ours DynamicObjectwe will stupidly parse a string InvokeEachMethod...and then respond to it accordingly. In this case, we will use reflection. Of course, this means that any use of this functionality as a DSL is a) completely undocumented and incomprehensible to a third party; and b) limited by rules for naming identifiers. For example, you cannot compile the following:

    DateTime dt = (DateTime)timeDO.friday.13.fullmoon;

    But then it’s friday13possible to compile . However, now there are (and probably are used in production) extension methods like July()that allow you to write very cryptic code like 4.July(2010). As for me, this is not cool at all.

    Links to examples


    Here are some examples of how intelligent people use the mechanism DynamicObjectfor their infernal goals:


    In short, there are a lot of use cases, although this disgrace cannot be called unconditionally “canonical” programming. I’m sure that the lack of static checks can result in a bunch of undetectable bugs when used improperly, so my advice is to be careful!

    Also popular now: