
Get to know DynamicObject

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
DO
and inherit from DynamicObject
:class DO : DynamicObject {}
Now, using the keyword,
dynamic
we can invoke some method on this object without any remorse:dynamic dobj = new DO();
dobj.NonExistentMethod();
Guess what we get. We
RuntimeBinderException
get 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 binder
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 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 true
if everything went well or false
if everything went wrong. The return false
from this method will just raise the exception that we saw above.What are there other than methods?
A set of overloaded operations for
DynamicObject
impressive. 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
this
you will receive a static object DO
instead 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
This
from 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
XElement
looks 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
DynamicObject
that 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
DynamicXmlNode
this 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
DynamicObject
we 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
friday13
possible 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
DynamicObject
for their infernal goals:- XML work
- Access to query results in ADO.Net (wow, does anyone else need this?)
- Wraps around stored procedures
- Capturing property changes
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!