So, we all know now that C# came with the capability of having
dynamic objects. We all know it came also with the nice
ExpandoObjetc class that permits you to use those capabilities in a very easy and straightforward way. But what happens when you want to use a multi-level dynamic object?
Let me elaborate it a bit more. Simply speaking, the normal objects you use regularly are non-dynamic objects, meaning that the compiler uses the early-binding behavior by which it checks that the object's properties and methods exist at compile time. So, with them, if you use a property or a method that don’t exist in the class definition, your code won’t compile.
But when you use the new
dynamic keyword to declare an object, you are instructing the compiler to use a late-binding mechanism, by which all those checks are postponed until run-time. In other words, when using a dynamic "type", the compiler understands that the actual type of the object is not going to be known until run-time, and that only then it is valid to perform the actual binding.
The nicest thing is that when the actual object used inherits from
DynamicObject, or if it implements the
IDynamicMetaObjectProvider interface, then you are allowed to intercept this late-binding mechanism and tell back the DLR (the dynamic analogous of the CLR) the way you want to deal when accessing those properties, or invoking those methods, that didn't exist in the class definition. For instance, a popular way is by by accessing a dictionary where to store the values of the properties along with their names, etc. (actually, this is pretty much what the
Now, what if you want one of those dynamic
properties to be a dynamic one itself? I mean one that is able to have its own
set of second-level dynamic properties? So what happens if you want to use the
dynamic employee = new ...;
employee.Id = "007";
employee.Name.FirstName = "James";
employee.Name.FamilyName = "Bond";
The above scenario is not supported by any object I was aware of. On top of that, I wanted such an object to be serializable (so serializing whatever arbitrary contents it may hold), and disposable (so that I can guarantee no references to any object are kept if I invoke its
There is no limit in the depth of the dynamic structure you are creating. And, of course, no restrictions at all to the type of values you can set into any of those dynamically created properties. Finally, you can also use indexes to create and access some properties, as we can see in the next example:
employee.Address = "MI-5";
employee.Address = "London";
employee.Address = "United Kingdom";
You can use as many indexes, and of whatever types, as you need. Yes, a given index can have a null value as well. The only restriction is that the indexes list cannot be empty (which, by the way, mimics the restriction the C# compiler imposes).
DeepObject inherits from
DynamicObject, and overrides its
TryGetXXX() methods to permit the interception of the dynamic bindings. Internally it maintains a list of members where the new dynamic properties are stored, each of them being its own instance of
And this is the key fact: in this recursive way each of them are able to store either contents, to use them as regular dynamic properties to get or set these contents, or storing its own set of second-level dynamic properties. The
TryGetXXX() methods are overridden in such a way that deal with all the complexities on your behalf.
DeepObject instances have just one major property:
Deep, of type
DeepCarrier. Its mission is to store all the internal details without polluting your dynamic instance with methods and properties, and by using it you can get access to a more advanced manipulation of your
Deep has itself the following set of properties and methods:
Members property permits the enumeration over the list of dynamic members. As expected, the
Count property returns the number of members the instance has – note that this count only reflects its direct members, not the nth-level members it may conceptually have.
- The idea is to let you use the dynamic syntax capabilities, as in the example, to automatically add the members you will need. But, if for whatever reason you need to add manually a member, instead of performing a dynamic binding you can do so by using any of the
AddMember() methods. You can also remove a member using the
RemoveMember() method, or clear all members by using the
- Sometimes is useful to refer to a given member by the index in which it is stored in the internal list. To obtain this index you can use the
Name property returns the name used for this instance when it was dynamically created. Note also that you can give a name to these instances when you create them by using their constructors, something I found useful in some circumstances. If a given instance is hosted into another one, its
FullName property returns the concatenation with dots of the full name of its parent plus its own given name, in a recursive way.
- Note that when this member is an indexed one, its name is assigned automatically by using a deterministic algorithm (that basically concatenates the values of the indexes used to instantiate this member).
- It is obvious that two members cannot share the same name, and the default behavior is to perform this comparison using a case-sensitive comparison. But you can set the mode to be case-insensitive by using the appropriate parameter in the constructor. In this case, this setting is propagated also to any child member is automatically created by the dynamic binding mechanism. You can get this setting by using the
- Also, it may happen that at a given moment in time you may want to block the object’s structure, so not allowing to add any more members or remove any of them. You can get or set this behavior by using the
- You can also get what value this member store, by using its
Value property. If no value has been assigned to it, it returns null by default. You can find out if a value has been assigned to it or not in its
HasValue property. Also, you can clean the value (meaning that
HasValue will return false again) by using the
IsIndex will return true if the member is an indexed one, or false otherwise. An indexed member is one that requires an index, or several ones, to access it.
Host will return the hosting instance of this one as a member, or null if it has no parent (it is a standalone instance and not a member of anyone).
Level will return the level of this object: cero if it is not a member of anyone, 1 if it is a first-level member, 2 if it is a second-level member, and so on.
- In this version I have dropped support for dynamic multi-level methods, because I have never end up using them. But if this causes a major problem, it won’t be hard to include them again, just let me know.
- [v3, August 2012]: cleaner architecture and performance improvements. Dropped support for dynamic methods.
- [v2, October 2010]: new methods and features added.
- [v1, May 2010]: initial version.