It sounds like a joke but there really is a class called ExpandoObject. When I first heard of it I thought it was a farce, but it is actually pretty useful. [Let me preface this by saying I wouldn’t necessarily use this class in production code because it has the potential to be a maintenance nightmare but it’s a good place to learn about dynamic objects.] The ExpandoObject is a DyanamicObject and a DynamicObject is essentially an object that accepts and brokers member calls (methods and properties) made under the dynamic context (sort of like a proxy). ExpandoObject is a type of DynamicObject with a very specific implementation; it defines and overrides specific operations that can be performed on an object (like an actual method call or property set). It then creates properties at runtime that can be executed. The full documentation of the ExpandoObject can be found here.
So what does it mean to create properties at runtime? With ExpandoObject you can do things like this:
// Create an ExpandoObject. Declare it "dynamic".
dynamic ex = new System.Dynamic.ExpandoObject();
ex.Name = "test"; // Creates a simple property of type string.
In this code example I’ve created an instance of ExpandoObject and assigned it to dynamic variable ex. Variables marked as dynamic are not compile-time checked, but are runtime resolved.
ex.Name is the first property I create. I am going to make it of type string by assigning it a string value. This compiles fine even though I’ve never defined the property “Name;” I am going to let ExpandoObject create it for me.
Under most circumstances, ex.Name would fail at runtime because there is no runtime implementation. In other words if you were to do run the following code you would get an error:
dynamic v = new Object();
v.Name = "test";
An exception is thrown because “Name,” even though it compiled, does not exist at runtime.
You can also define methods on ExpandoObject.
// We can also create functions (this action takes no parameters).
ex.PrintToConsole = (Action)(() => Console.WriteLine("some function"));
// Creating methods that take a parameter.
ex.PrintWithParam = (Action<string>)((string s) => Console.WriteLine(s));
// Creating methods that take a parameter and return a value.
ex.MethodWithReturn = (Func<int, int>)((int i) => i * i);
In the code example above, I defined the method PrintToConsole to be a delegate of type action (does not take any parameter and returns void); you can also create delegates of type Action<> and Func<>…this is a very slick way of defining dynamic methods.
So how does this all work? How does the ExpandoObject know to intercept method calls and define properties? Well, as I mentioned above the ExpandoObject is a DynamicObject (it’s actually an IDynamicMetaObjectProvider, but DynamicObject implements IDynamicMetaObjectProvider). There are many overridable methods in DynamicObject but for our purposes the three most important are: TryGetmember(,,.), TrySetMember(…), and TryInvokeMember(…). When implemented in a subclass, the DLR will call the appropriate method during runtime. So, for instance, when a property is set on an instance of ExpandoObject the DLR calls TrySetMember (with some parameters) on ExpandoObject then ExpandoObject does stuff and returns either true or false; true if the call was successful (which is always is), false otherwise. If false is returned you will get the error above (object does not contain a definition for blah blah blah). I wrote an absurdly simple implementation of my own ExpandoObject for demonstration purposes.
Not much to it, really. When a setter is called, TrySetMember will be executed. The binder parameter knows the name of the property that was set….so if you have ex.MyProp = “text”, the binder.Name property will be set to “MyProp.’ In my case I use this as a key into a hashtable…the value is the second parameter. Now when a get member is executed, TryGetMember will be executed with similar parameters: the name of the property that was executed and the result, which you must set before exiting the method. The result is literally the result of the operation. So if I have a property called MyProp with a value “text,” when I go to print that to the screen I should see “text,’ because that’s what I stored in the dictionary when the property was set.
TryInvokeMember is a little more interesting. This is called when a method is executed on a dynamic object (like in my first code example when I defined the delegates). So if I do something like ex.Something(), TryInvokeMember will be executed with all the appropriate parameters set. Since Action, Action<>, and Func<> are all delegate types, I cast the value from the dictionary to a Delegate then dynamically invoke it with the arguments provided.
Using MySimpleExpandoObject in code.