The other day an interesting question was posed on Stackoverflow.
Sumarizing:
"How can I add methods to a type dynamically at runtime and have them be
available to all other instances of that type?"
That is certainly an interesting question. Of course this is
similar to how
JavaScript works via the prototype property that all objects
have. So I set out to see how easy it would be to simulate prototypal
inheritance in C# using the new
Dynamic Language Runtime features
introduced in C# 4.0.
Now let me preface this by saying this solution is an
answer in search of a question. That is, there is no particular problem I
can immediately think of that this solves. It is a decent demonstration of
the dynamic capabilities available to you in C#, but if you find yourself
needing this kind of behavior, you might be a closet hipster.
What would
such an implementation look like? Imagine the following class
structure:
public class Foo {}
public class Bar: Foo {}
And now we would like to be able to dynamically define methods for Foo
and Bar at runtime like so.
var foo1 = new Foo();
var foo2 = new Foo();
Foo.Add = new Func<Int32,Int32,Int32>((a, b) =>; a + b);
var result1 = foo1.Add(5, 5); // result1 == 10
var result2 = foo2.Add(10, 5); // result2 == 15
As it is written, this is impossible since it won't even compile.
However, if we leverage the DLR and the
dynamic keyword, then we can begin to
make some magic happen.
ExpandoObject is certainly an attractive new addition
to .Net, as it allows us to dynamically add members at runtime, but they are
local to that instance only. So for our purposes we are going to have to turn
to the low level
DynamicObject. DynamicObject gives us hooks into the new
dynamic features of the runtime, but we are forced to write all the necessary
plumbing to get the functionality we want. At a minimum we need to be able
to:
- Add new properties
- Add new methods
- Retrieve the value of dynamic properties
- Invoke dynamically added methods
This turns out to be pretty easy by overriding just three methods from
DynamicObject.
TrySetMember,
TryGetMember and
TryInvokeMember.
public class ProtoObject : DynamicObject
{
private Dictionary<String,Object> _members = new Dictionary<String,Object>();
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
Object member = null;
result = null;
//Check to see if we have a member of this name
var success = _members.TryGetValue(binder.Name, out member);
//Check to make sure it is a delegate that can be invoked
if (success && member is Delegate)
{
//Execute the member with the passed in arguments and assing the result
result = ((Delegate)member).DynamicInvoke(args);
}
return success;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
//Attempt to get the saved value with specified name
var success = _members.TryGetValue(binder.Name, out result);
return success;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
//Add the new member name and value to our dictionary
// or override the existing value
if (_members.ContainsKey(binder.Name))
_members[binder.Name] = value;
else
_members.Add(binder.Name, value);
return true;
}
}
Now we have a custom object that will act similar to how ExpandoObject
works. However,
we still haven't solved the issue of inheriting members
across all types. What we need is a shared collection of members that are
specific to that type.
I've chosen a static Dictionary<Type,ExpandoObject> for this
solution. Obviously we want to use Type as a key since the members should
be specific to a type, and ExpandoObject already gives us all the
functionality we need to store a collection of arbitrary properties and
methods.
In order to make this work properly, we need to initialize the Dictionary
with each new type in the inheritance chain, and provide a humane way of
adding new members to a specific type. The constructor can be used to
ensure that new types are added to the Dictionary, and by getting
creating with the dynamic keyword, we can add a Prototype property that
will give each type access to it's corresponding ExpandoObject.
private static readonly Dictionary<Type,ExpandoObject> _prototypes = new Dictionary<Type,ExpandoObject>();
public ProtoObject()
{
if (Prototype == null)
{
_prototypes.Add(GetType(), new ExpandoObject());
}
}
public dynamic Prototype
{
get
{
if (_prototypes.ContainsKey(GetType()))
return _prototypes[GetType()];
else
return null;
}
}
Finally, when a request for a member is made, we need to first check to
see if there is one defined on the instance. If we can't find one, then
we can begin looking for a matching member in the prototype object,
walking the inheritance chain until we find what we are looking for.
Since finding an appropriate prototype member by walking the inheritance
chain is recursive in nature, a helper method can be added to make this
easier. While TrySetMember remains unchanged, TryInvokeMember and
TryGetMember need to change slightly to accommodate the new
functionality, but not by much.
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
Object member = null;
result = null;
var success _members.TryGetValue(binder.Name, out member);
if (success && member is Delegate)
{
result = ((Delegate)member).DynamicInvoke(args);
}
if (!success)
{
member = FindPrototypeMember(binder.Name, GetType());
if (member is Delegate)
{
result = ((Delegate)member).DynamicInvoke(args);
success = true;
}
}
return success;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var success = _members.TryGetValue(binder.Name, out result);
if (!success)
{
var member = FindPrototypeMember(binder.Name, GetType());
if (member != null)
{
result = member;
success = true;
}
}
return success;
}
//Walk the inheritance chain using recursion to find a suitable
// prototype member
private object FindPrototypeMember(string memberName, Type type)
{
if (String.IsNullOrWhiteSpace(memberName) || type == null) return null;
if (!_prototypes.ContainsKey(type)) return null;
var prototype = _prototypes[type] as IDictionary;
if (prototype.ContainsKey(memberName))
return prototype[memberName];
else
return FindPrototypeMember(memberName, type.BaseType);
}
Putting this all together allows us to inherit from this object and
dynamically add members and properties at runtime that will be available
to all instances of that type. In other words, given this class
structure:
public class Foo: ProtoObject {}
public class Bar: Foo {}
This code is totally valid:
dynamic myFoo = new Foo();
dynamic yourFoo = new Foo();
dynamic myBar = new Bar();
myFoo.Prototype.Name = "Josh";
myFoo.Prototype.SayHello = new Action(s => Console.WriteLine("Hello, " + s));
yourFoo.SayHello(myBar.Name); // 'Hello, Josh'
Again, I can't think of any practical use for this off the top of my
head, but it is interesting to see just how much flexibility you can
squeeze out of C# with very little code.
For those who are interested
a more robust version of this code is available on GitHub. The unit tests are the best documentation of the
additional features, mainly:
- Ability to check for "undefined" members by returning a special
value instead of throwing an exception
- Ability to access the current instance from within dynamically
added methods.
- Allows original exception with full stack trace to propogate
instead of a RuntimeBinderException.