Practical .NET

Working with Dynamic Objects: Beyond the Basics with ExpandoObjects

ExpandoObjects let you dynamically add members to your object at runtime -- a great way to handle scenarios where you need a lot of flexibility. But how do you work with an object when you don't know the names of its properties?

In an earlier column I discussed the basics of working with ExpandoObjects (including how to add methods to an ExpandoObject). As I said in that column, there's nothing stopping you from building an ExpandoObject in one part of your application with a method like this:

public ExpandoObject CreateDynamicCustomer(string Name)
{
  dynamic cust = new ExpandoObject();
  cust.FullName = Name;
  cust.ChangeName = (Action<string>)((string newName) =>
            {
                cust.FullName = newName;
            });
  return cust;
}

You can then call that method from another part of your application to grab the resulting ExpandoObject:

dynamic cust = CreateDynamicCustomer("Peter Vogel");

Working with Dynamic Properties
There's one problem with all of this, though: Imagine, for example, a scenario where you're reading a CSV file and parsing its data into a set of ExpandoObjects. You're using ExpandoObjects because each CSV file has a different set of columns and you want the property names on your ExpandoObject to reflect those column names.

Using an ExpandoObject would let you add properties for each of the columns defined in the file -- the method that reads the file would use the first line of the file to determine the names to be used for the properties. Except, how do you set properties whose names you don't know at compile time?

Fortunately, with ExpandoObjects, you can use the same syntax as you would with a Dictionary to store your values: Just cast your ExpandoObject to the IDictionary interface and use your ExpandoObject like a Dictionary.

Here's a method that accepts both a property name and a property value then adds them both to an ExpandoObject:

public ExpandoObject CreateDynamicCustomer(string propertyName, string PropertyValue)
{
  dynamic cust = new ExpandoObject();
  ((IDictionary<string, object>)cust)[propertyName] = PropertyValue;
  return cust;
}

Though I've added the property through the IDictionary interface, I can retrieve it just like an ordinary property (though I won't get any IntelliSense support as I type in the property name):

dynamic cust = CreateDynamicCustomer("FullName", "Peter Vogel");
string fname = cust.FullName;

But that just leads to the next problem: If you're adding properties dynamically in one part of your application, how do you know, in another part of the application, what properties are available? The solution here, again, is to cast your ExpandoObject as an IDictionary object. You can then roll through all your properties with code like this:

foreach (KeyValuePair<string, object> kvp in ((IDictionary<string, object>) cust))
{
  string PropertyWithValue = kvp.Key + ": " + kvp.Value.ToString();
}

In that last column, I showed how to add methods to your ExpandoObject using lambda expressions. However, if you're writing code like this, then you probably don't want to work with the members on your ExpandoObject that hold lambda expressions. To filter out any members loaded with an Action expression (like the one in my example), you could use code like this:

foreach (KeyValuePair<string, object> kvp in ((IDictionary<string, object>) cust))
{
  if (!kvp.Value.GetType().Name.Contains("Action"))
  {
    foreach (KeyValuePair<string, object> kvp in ((IDictionary<string, object>) cust))
    {
      string PropertyWithValue = kvp.Key + ": " + kvp.Value.ToString();
    }

It's also worth pointing out that, once you've cast your ExpandoObject to the IDictionary interface, you're free to use other methods built into the IDictionary interface, including ContainsKey (which, effectively, checks to see if a specific property is present) and TryGetValue (which returns false if a property isn't present and provides the property's value if it is).

Storing ExpandoObjects
If you're using an ExpandoObject to capture information, you're probably going to want to store that information somewhere. That means you'll need to convert your ExpandoObject into some "storable" format. Fortunately, if you add the NewtonSoft.Json NuGet package to your project, you can convert your object to a string with this single line of code:

string strCust = JsonConvert.SerializeObject(cust, new ExpandoObjectConverter());

To convert that string back to your original ExpandoObject, you'll use code like this:

cust = JsonConvert.DeserializeObject<ExpandoObject>(res, new ExpandoObjectConverter());

This will do a great job of serializing your properties and will normally ignore any methods you added (probably what you want). The one exception is if you have a method that refers to itself (as the example in my previous column did). In that scenario the serialization code will blow up.

If you don't like the way that the provided ExpandoObject works, by the way, you can build your own. For example, if you ask for a property on an ExpandoObject that doesn't exist, an ExpandoObject object will throw an exception. In that scenario, you might prefer a class that returns null or Nothing. To get that class, create your own class that inherits from DynamicObject and then override some combination of its TrySetMember, TryGetMember and TryInvokeMember methods. In your new class, you'll probably end up storing your values inside a Dictionary. Here's a pretty good example of how to create your own dynamic object.

Whichever tool you use, your ExpandoObject will, in fact, be ready for anything. Just remember that you won't have IntelliSense to help you get there.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.

Upcoming Events