📜 ⬆️ ⬇️

Meet DynamicObject

Every time when you have a new interesting feature in the language, there are always people who start squeezing the maximum out of the feature. DynamicObject 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 the System.Dynamic.DynamicObject class is. This class seems to be ordinary - from it, for example, one can follow and overload one or more of its methods. Only here are some difficult methods ... let's take a closer look.

First we make a DO test object and inherit from DynamicObject :

class DO : DynamicObject {}

Now, using the dynamic keyword, we can invoke any method on this object without any remorse of conscience:
')
dynamic dobj = new DO();
dobj.NonExistentMethod();

Guess what we get. We RuntimeBinderException something called RuntimeBinderException and here’s the message.

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

which is natural, because the NonExistentMethod() method in our class simply does not exist. And the interesting thing is that it may never be. That's the whole DynamicObject - the ability to call properties and methods that the class doesn't 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 the method

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

In the case of a call to the NonExistentMethod() method, this method is called without arguments, and the binder parameter just 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 somehow be processed. How - it's up to you. There can be any business logic. Again, there are mechanisms for obtaining arguments ( args ) and returning a result ( result ). The method returns true if everything was successful or false if everything was covered. Returning false from this method just causes an exception that we saw above.

What is besides methods?


The set of overloaded operations for DynamicObject impressive. This is primarily the ability to respond to access to properties that do not exist, as well as to conversions, unary and binary operators, access via an index, etc. Some operations are generally not intended for C # / VB — for example, intercepting object creation, deleting object members, deleting an object by index, etc.

There is one small incident - through this you will receive a static DO object instead of a statically typed dynamic 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 This from TryInvokeMember() because you simply get StackOverflowException .

ExpandoObject


ExpandoObject 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();

Serialization of such an object is certainly not an easy task. 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. No matter. If you really need to, you can use the System.Runtime.Serialization.DataContractSerializer :

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

Naturally, the methods such a thing will not be serialized. For this, you can organize dances with a tambourine around DataContractResolver , but the purpose of this article is not to use such practices.

What to do with it?


OK, in general, the functionality is clear from the point of view of COM-development, in which every more or less serious interaction is similar to clearing the Augean stables. Interaction with dynamic languages ​​is also a good plus, and if I’m at least somewhat interested in this, I would certainly in this article tell about those bindery and other infrastructural charms to which all this applies.

Here is a very good example, which is quoted wherever possible (so I hope this is not plagiarism). The bottom line is that when working with XML, access to elements and attributes of XElement looks simply inhuman:

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

This is just non-human syntax. Here is a much more “glamorous” solution: first we make DynamicObject , which with its virtual properties resolves the contents of 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 it's up to the 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 AOPs


Once again I want to note that with the ability to control access to objects like this, we can attach AOP in the style of Unity.Interceptor or another IoC + AOP framework that works on dynamic tools. For example, in the example just above, we can guarantee that we will never have a NullReferenceException thrown, even if one of the elements in the chain is really 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 based on this, which will not be statically checked (unlike the MPS style syntax), 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?” Actually, it all depends on how you look at it. For example, our example with DynamicXmlNode is DSL for which XML is a domain. In the same way, I can for example write the following:

myobject.InvokeEachMethodThatBeginsWithTest()

The moral is that in our DynamicObject we will stupidly InvokeEachMethod... line InvokeEachMethod... and then react 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 an outsider; and b) is limited to identifier naming rules. It will not be possible for example to compile the following:

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

But here compile friday13 will work. However, there are already (and probably 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 DynamicObject mechanism for their infernal purposes:


If it is short, then there are a lot of use cases, although it is certainly not called “canonical” programming. I am sure that the lack of static checks can, when illiterately used, produce a bunch of undetected bugs, so my advice to you is to be careful!

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


All Articles