Click here to Skip to main content
Click here to Skip to main content

DeepObject: A Multi-level C# 4.0 Dynamic Object

By , 15 Oct 2012
 

Introduction

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 ExpandoObject does).

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 following code?:

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 Dispose() method).

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[0] = "MI-5";
employee.Address[1] = "London";
employee.Address[2] = "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’s basics

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 DeepObject.

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 DeepObject instances. Deep has itself the following set of properties and methods:

  • The 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 Clear() method.
  • 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 IndexOf() method.
  • Its 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 CaseSensitiveNames property.
  • 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 Sealed property.
  • 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 ResetValue() method.
  • 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.

Changes

  • 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.

History

  • [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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Moises Barba
Spain Spain
Member
Moises Barba works for the Consulting division of a major multinational IT company.
While he has not to develop for a living nowadays, solving complex puzzles has been ever among his main interests - that's why he has spent his latest 20 years trying to combine his degree is in Theoretical Physics with his MBA... and he is still trying to figure out how this two things can fit together.
Flying a lot across many countries, along with the long working days that are customary in consultancy, he can say that, after all, he lives in Spain (at least the weekends).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHow to serialize to Json?memberMember 469702022 Oct '12 - 16:36 
Hi MB, great work on the DeepObject. Thumbs Up | :thumbsup:
 
However, I see there is ToString() that serializes to a format similar to Json but it is not Json.
 
I use Json.Net to serialize but it dumps a lot of other unnecessary metadata as well. Thanks!
AnswerRe: How to serialize to Json?memberMoises Barba22 Oct '12 - 23:04 
Hello. As you have mentioned, DeepObject's ToString() was intended to provide a string representation of the contents of the instance where it is called, but not to provide it in JSON format.
 
Having said that seems to me it wouldn't be very hard to build such functionality. At first sight it would be a matter of serialize recursively the properties it holds. But, as I'm not an expert on JSON internals, I'm not sure how would I serialize information about the type of the value such properties store. Maybe you could point me at some place where I can find this information/trick/how-to?
 
Cheers, Moises
SuggestionRe: How to serialize to Json?memberMember 469702023 Oct '12 - 16:29 
The only difference for DeepObject should be to auto new ExpandoObject() for child level.
 
            dynamic exp = new ExpandoObject();
            exp.Name = "user";
            exp.Address = new ExpandoObject();
            exp.Address.Line1 = "line1";
 
            result = Newtonsoft.Json.JsonConvert.SerializeObject(exp);
            //result is {"Name":"user","Address":{"Line1":"line1"}}
 
If possible, I suggest that you need not develop your own JSON serializer but using existing libraries such as Newtonsoft.Json. Hope this helps.
QuestionMy vote of 5memberAnkush Bansal14 Oct '12 - 23:59 
Thanks or sharing this information.
Does the DeepObject implements INotifyPropertyChanged and thus have binding support like ExpandoObject have?
AnswerRe: My vote of 5memberMoises Barba15 Oct '12 - 5:02 
Hello and thanks for your message.
 
No, DeepObject does not currently support INotifyPropertyChanged: I have not yet figure out a nice syntax that could be used in a "multi-level" environment, as it is the use case that DeepObject was designed for.
 
By "multi-level dynamic object" I mean that its first-level properties can also be dynamic (DeepObject) ones, and so they could carry their own second-level dynamic properties, that in turn could carry their own third level... and so forth, permitting to add and use them in a dynamic, natural and on-demand fashion.
 
It does already support other interfaces (ISerializable, ICloneable, IDisposable), so I will be happy to implement INotifyPropertyChanged if there is any suggestion on what would be the most handy sintax to use this feature afterwards.
 
Thanks, Moises
GeneralMy vote of 4memberbartolo3 Sep '12 - 5:25 
Interesting stuff, thanks.
GeneralRe: My vote of 4memberMoises Barba25 Sep '12 - 20:48 
ok, thanks
GeneralMy vote of 5membervisalia2 Sep '12 - 8:04 
You do deserve a 5!
GeneralRe: My vote of 5memberMoises Barba25 Sep '12 - 20:48 
Thanks sir
GeneralMy vote of 5membermbsmbs30 Aug '12 - 5:01 
good job
GeneralRe: My vote of 5memberMoises Barba30 Aug '12 - 7:07 
Thanks
AnswerReally a bit basic and out of datememberClifford Nelson29 Aug '12 - 13:47 
This was fine when dynamic first came out, but would expect an article with more depth if it is published today. Way to basic to be worth republishing. Also refers to the new C# 4.0, which is no longer new.
GeneralRe: Really a bit basicmemberMoises Barba29 Aug '12 - 13:50 
Thanks for your comment.
 
Let me mention that this piece is part of a bigger ORM solution I'm actually publishing now, so my intention was that, instead of "polluting" the ORM article's descrition with this piece, it has sense, in my modest opinion, to have this new version published in its own one.
 
Anyhow, I have not seen any similar solution elsewhere.
 
Thanks, Moises.
GeneralRe: Really a bit basicmemberClifford Nelson30 Aug '12 - 6:27 
spelling error: ExpandoObjetc
Generalhiiiiiiiiiigroupglory44415 Apr '11 - 8:58 
Hello ,
How are you today ? .
I hope you are fine and all is well with you, My name is miss glory i saw your profile at codeproject.com which really interest me and i decide to communicate with you, and it will please me if you will be my friend, Here is my direct email address (glory_west10@yahoo.in),so that i will tell you more about me and my picture hope to hear from you
Glory.
( glory_west10@yahoo.in)
GeneralAmazingmemberjapp2710 Dec '10 - 7:45 
Difficult to understand but the concept of "dynamic" is amazing! I had a long standing issue which i promptly solved with "dynamic". Thanks!
GeneralRe: Amazingmembermbarbac10 Dec '10 - 21:19 
Appreciate your comment.
 
Maybe the best way to explain it in this context is that instead of "coding in advance" the properties and methods of an object, the concept of "dynamic" permits you to define such properties and methods when you are actually in run-time. Purist will kill me for this explanation, but hope it helps you.
 
Thanks, Moisés.
GeneralRe: Amazingmemberwonderdelight18 Jan '11 - 3:36 
I do find this amazing, however what is even more amazing is how old patterns, technologies are finally coming back. Back in 2000 I developed distrubuted com+ applications, all of which was n-tier DNA architecture using Com+.
 
The middleware architecture provided an interface which accepted XML (before message queue came out) which received the XML request. This requesrt contain the method name, parameters. THis XML stream would be validated within BL objects and if ok would late binding and call the relevant object with its associated parameters.
 
This article discusses late binding and its ability to solve some issue, but it existed 10 years ago in Com+.
GeneralerrormemberJohn Giblin15 Nov '10 - 17:36 
I am getting this
The name 'TypeHelper' does not exist in the current context
GeneralRe: errormembermbarbac17 Nov '10 - 1:40 
Thanks for your message John. I have to apologize, I didn't include the TypeHelper.cs file, an unforgivable slip.
Fortunately, you only need a few lines, as follows:
 
public static class TypeHelp {
  internal static Delegate _CreateConverterDelegate( Type sourceType, Type targetType ) {
    var input = Expression.Parameter( sourceType, "input" );
    Expression body;
    try { body = Expression.Convert( input, targetType ); }
    catch( InvalidOperationException ) {
      var conversionType = Expression.Constant( targetType );
      body = Expression.Call( typeof( Convert ), "ChangeType", null, input, conversionType );
    }
    var result = Expression.Lambda( body, input );
    return result.Compile();
  }
  public static object ConvertTo( object source, Type targetType ) {
    if( targetType == null ) throw new ArgumentNullException( "Target Type" );
    Type sourceType = ( source == null ) ? typeof( object ) : source.GetType();
    Delegate converter = _CreateConverterDelegate( sourceType, targetType );
    return converter.DynamicInvoke( source );
  }
  public static T ConvertTo<T>( object source ) {
    T target = (T)ConvertTo( source, typeof( T ) );
    return target;
  }
}
 
Hope it helps. Best regards, M
GeneralRe: errormemberJohn Giblin18 Nov '10 - 12:32 
that did it. thanks. great article.
GeneralMy vote of 3memberBilal Haider27 Oct '10 - 22:27 
complex issue but not explained up to the mark
Generalbindingmembercqwydz17 Oct '10 - 7:19 
can it binding property in silverlight?
GeneralRe: bindingmembermbarbac18 Oct '10 - 12:05 
Thanks for your message. I haven't tried - but as far as it does support C# dynamics, if it is the case, I don't see why not.
GeneralDynamic Objects and Why....memberxzz019515 Oct '10 - 3:30 
When I first read about this new feature in .NET 4.0 I thought OMG MSFT is actually dumbing down C#. I couldn't for the life of me figure out the reasoning behind this. As a strong supporter of strongly typed languages and the massive benefits they bring, I just thought this wasn't a good idea.
 
But then having worked in WPF, coming from the Windows Forms world with the excellent support of the .NET wrapper for the System.Windows.Forms.WebBrowser class , and trying to work with the WPF WebBrowser class, I found that MSFT did not decide to add additional .NET Wrapping to improve on the Forms Browser, rather they went the other way and decided to force you to go towards the COM implementation of the browser. As a result of this, there was much more power in parsing the HTML DOM object (IE itself using the "Trident" engine has already parsed the document in order to display it).. So why use a wrapper? The answer was convienience for the .NET world. But many things were missing from the wrapper. Thus articles like this The most complete C# Webbrowser wrapper control. Today, that type of support is somewhat obsolete with the dynamic support.
 
With the new Dynamic support, one no longer needs to do endless casting to obtain methods and properties for unknown objects. The Dynamic support allows you to just call the method without knowing what type it is. So now one can do this: Dynamic htmldoc = Webbrowser.Document, they type of which will be any supported document type. Most of the time a person will want the HTMLDocument class, but in WPF this is the MSHTML.DocumentClass which requires massive amounts of casting to morph the content into types of which access to any member in the class is possible. In the past that casting was problematic because intellisense doesn't work too well with COM. A developer had to read the References for the MSHTML class and test the code with each new thing added. Addtionally, there was no Immediate Window support to "experiement" when single stepping through code. Quite often Watches on Vars didn't work either due to COM object states not allowing it. Countless hours were spent trying to just "understand" how to work with MSHTML. Not any longer because the dynmaic object allows one to do one sided casting...
 
So now if I want an interface, all that's needed is this Dynamic interface3 = (mshtml.IHtmldocument3)document. I can now morph the document and call methods without casting on the left side. Here's another example, I can use Dynamic to get all htmlelements like this Dynamic elements = document.all. There is no left side casting. I can now test for null and parse accordingly. I can allow call any members of the elements object without casting. Using Dynamic support for MSHTML object is really nice. Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 15 Oct 2012
Article Copyright 2010 by Moises Barba
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid