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:
dynamic ex = new System.Dynamic.ExpandoObject();
ex.Name = "test";
Console.WriteLine(ex.Name);
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";
Console.WriteLine(v.Name);
An exception is thrown because “Name
,” even though it compiled, does not exist at runtime.
You can also define methods on ExpandoObject
.
ex.PrintToConsole = (Action)(() => Console.WriteLine("some function"));
ex.PrintWithParam = (Action<string>)((string s) => Console.WriteLine(s));
ex.MethodWithReturn = (Func<int, int>)((int i) => i * i);
ex.PrintWithParam("hello");
Console.WriteLine(ex.MethodWithReturn(9));
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 it 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.
CodeProject