Practical .NET

Making It Up as You Go Along with ExpandoObjects

You can give your users the ability to store any data they want, including stuff they make up at run time, by using an ExpandoObject. In fact, when you don't know what your data is until run time (and you can live without IntelliSense), an ExpandoObject is your best solution.

I was talking with some developers who'd just taken Learning Tree's C# Microsoft Official Curriculum course with me. They were looking at the ExpandoObject object as a way to support creating an application that would process several different kinds of CSV files with each file having a different set of columns and data. It was an interesting conversation because they'd picked exactly the right class to work with: When you don't know what you're going to need at run time, an ExpandoObject is just what you need.

Usually, of course, we know what data we're going to be working with in advance: We'll have a Customer object with properties like Id, FirstName, LastName and so on. Sometimes, as with reading CSV files with different layouts, we don't. In fact, any scenario where you want to give clients (or users) the ability, at run time, to store any values they want is a good scenario for using an ExpandoObject.

This may make ExpandoObjects sound like anonymous objects. But ExpandoObjects are more flexible in at least two ways. First, you can add properties and methods at any point in an ExpandoObject object's life, not just when the object is created. In addition, unlike an anonymous object, you can return an ExpandoObject from a method, allowing you to pass it around your application.

The problem with ExpandoObjects (and I'm warning you up front) is that you'll have to give up the support that IntelliSense gives you when writing your code. IntelliSense depends upon the definition of your classes to prompt you through writing code that works with a class. If you don't know, until run time, what members your object will have, then you can't expect IntelliSense to know at compile time.

Storing Data in an ExpandoObject
The ExpandoObject class lives in the System.Dynamic namespace, part of the .NET Framework's initiative to support dynamic languages. Working with classes in this namespace does require a different way of thinking about objects. For example, an ExpandoObject lets you add properties or methods to an object dynamically, just by naming them and assigning a value.

It's not quite as straightforward as it sounds, though. This code, for example, looks like it creates an ExpandoObject named cust object and gives it a FullName property, set to "Peter Vogel":

ExpandoObject cust = new ExpandoObject();
cust.FullName = "Peter Vogel";

This code won't, however, compile because the compiler checks the class's structure at run time (this is what enables IntelliSense, after all). That process quickly determines that the definition of ExpandoObject doesn't include a FullName property. You need, therefore, to defer this kind of type checking until later in the process -- until run time, to be exact. To do that, you just need to change the type of cust object from ExpandoObject to dynamic (note the lower-case "d").

The code that will compile will look like this:

dynamic cust = new ExpandoObject();
cust.FullName = "Peter Vogel";

Declaring a variable as dynamic causes .NET to defer checking for the existence of any member (method or property) until the member is actually used. It's only at that point, if the object doesn't have the member, that the code will blow up.

Adding Methods to an ExpandoObject
You can also add methods to your ExpandoObject object just by inserting a lambda expression into the member. Be aware, though, that adding an ExpandoObject with methods may be more trouble than its worth, as you'll start to see here. Again, you might think that the code to add a lambda expression to your ExpandoObject might look like this:

cust.ChangeName = (string newName) =>
            {
                cust.FullName = newName;
            };

However, this code won't compile either. This time, however, it's because of the type of the lambda expression. You'll need to do a cast to tell .NET that you're trying to use a lambda expression (you'll also need to enclose your lambda expression in parentheses).

Since my lambda expression doesn't return a value but does accept a single parameter of type string, my expression needs to be cast as an Action<string> type, like this:

cust.ChangeName = (Action<string>) ( (string newName) =>
            {
                cust.FullName = newName;
            } );

Note: Inside the lambda expression, if you want to work with a property on the local variable (as I do here), you must refer to the variable that's holding your method. Omitting that reference or using the this keyword won't work. Either way, you'll end up referring to the class that this code is inside of, not to the ExpandoObject that the lambda expression is being added to.

However as odd as that code looks, the code to call the resulting method is perfectly ordinary. Here, for example, is the code to call my ChangeName method:

cust.ChangeName("Jan Vogel");

If you want to have a method that returns a value, then you'll need to cast your lambda expression as a Func. The first types you reference in a Func declaration represent any parameter values you pass in (as in my Action declaration earlier). With a Func, though, the last type referenced specifies the type that the lambda expression returns.

This variation on my ChangeName method accepts a string parameter and returns an integer value, so its Func declaration is Func<string, int>:

cust.ChangeName = (Func<string, int>) ( (string newName) =>
            {
                cust.FullName = newName;
                return cust.FullName.Length;
            } );

That's the basics of creating ExpandoObjects. There's still some stuff to cover, though. How do you add a property whose name you won't know until you read it from a file? How do work with an object whose property (and method) names were set somewhere else in your application? And how do your store this thing in a database? I'll cover all those questions in a later column.

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

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube