Prototypal Inheritance in .NET: Delegation at Last
In .NET 4.0, we have access to the Dynamic Language Runtime (DLR) giving us dynamic dispatch via the IDynamicMetaObjectProvider interface coupled with the dynamic keyword. When a variable is declared as dynamic
and it implements the IDynamicMetaObjectProvider
interface, we are given the opportunity to control the delegation of calls on that object by returning a DynamicMetaObject
containing an expression which will be evaluated by the runtime. We only get this opportunity if the direct target was unable to directly handle the expression.
The DynamicObject
and ExpandoObject
classes both implement IDynamicMetaObjectProvider
, but their implementations are explicitly implemented and the nested classes used to return the DynamicMetaObject
are private and sealed. I understand that the classes may not have been tested enough to make them inheritable, but not having access to these classes really hurts our ability to easily modify the behavior of the underlying DynamicMetaObject
. The key word here is easily; implementing the needed expression building is a great deal of work and the internal workings of the Microsoft implementations leverage many internal framework calls.
A key thing to consider is that we don’t want to replicate classical inheritance; instead, we are going to focus on prototypal inheritance that mostly replicates JavaScript’s prototypal inheritance. Rather than trying to replicate all of the hard work that the DRL team put into writing their implementation, we can add our own implementation on top of theirs. It is simple to hook in, but we need to save off a method to access the base DynamicMetaObject
implementation. This will allow us to attempt to interpret the expression on the object itself or pass it along.
public class DelegatingPrototype : DynamicObject {
public DelegatingPrototype( object prototype = null ) {
Prototype = prototype;
}
public virtual object Prototype { get; set; }
public override DynamicMetaObject GetMetaObject( Expression parameter ) {
if ( Prototype == null ) {
return GetBaseMetaObject( parameter );
}
return new PrototypalMetaObject( parameter, this, Prototype );
}
public virtual DynamicMetaObject GetBaseMetaObject( Expression parameter ) {
return base.GetMetaObject( parameter );
}
}
This small amount of code just sets the hook. Now we need set up the delegation expression.
To set up the prototypal hierarchy, we are going to need to do a lot of recursion. Unfortunately, it is well hidden (I’ll explain shortly). When a call is made on the root object, we are given the expression being interpreted. Using the IDynamicMetaObjectProvider
overload, we will hand off the expression to the PrototypalMetaObject
to construct delegation expression (or the DynamicObject
implementation if our prototype is null
thus trying to interpret the expression on the current object). We want to make a preorder traversal of the prototype hierarchy; at each step, the current object’s evaluation will take precedence over its prototype tree. Consider the following prototypal class hierarchy:
Since we never know which level of the hierarchy will be handling the expression, we need to build an expression for the entire tree every time. We want to get the DynamicMetaObject
representing the current object’s tree first. Once done, we get the DynamicMetaObject
for evaluating the expression on the current instance. With these two, we can create a new DynamicMetaObject
which try to bind the expression to the current instance first, and then fallback to the prototype. At the root level, the prototype DynamicMetaObject
contains the same fallback for the next two layers.
There is another caveat that we need to address. When we try to invoke and expression on an object, the expression is bound to that type. When accessing the prototype, if we don’t do anything, the system will throw a binding exception because the matching object won’t match the DynamicMetaObject
’s type restrictions. To fix this, we need to relax the type restrictions for each prototype.
Remember the recursion I mentions earlier? In the code sample below, I have pulled out all binding code except for the BindInvokeMember
method. The _metaObject.Bind[...]
will actually call into DelegatingPrototype::GetMetaObject
which will try to call back into _metaObject.Bind[...]
, which will…well you get the idea. At each call, the prototype becomes the target and we get a new prototype.
public class PrototypalMetaObject : DynamicMetaObject {
private readonly DynamicMetaObject _baseMetaObject;
private readonly DynamicMetaObject _metaObject;
private readonly DelegatingPrototype _prototypalObject;
private readonly object _prototype;
public PrototypalMetaObject( Expression expression, DelegatingPrototype value, object prototype )
: base( expression, BindingRestrictions.Empty, value ) {
_prototypalObject = value;
_prototype = prototype;
_metaObject = CreatePrototypeMetaObject();
_baseMetaObject = CreateBaseMetaObject();
}
protected virtual DynamicMetaObject CreateBaseMetaObject() {
return _prototypalObject.GetBaseMetaObject( Expression );
}
protected virtual DynamicMetaObject CreatePrototypeMetaObject() {
Expression castExpression = GetLimitedSelf();
MemberExpression memberExpression = Expression.Property( castExpression, "Prototype" );
return Create( _prototype, memberExpression );
}
protected Expression GetLimitedSelf() {
return AreEquivalent( Expression.Type, LimitType ) ? Expression : Expression.Convert( Expression, LimitType );
}
protected bool AreEquivalent( Type lhs, Type rhs ) {
return lhs == rhs || lhs.IsEquivalentTo( rhs );
}
protected virtual BindingRestrictions GetTypeRestriction() {
if ( Value == null && HasValue ) {
return BindingRestrictions.GetInstanceRestriction( Expression, null );
}
return BindingRestrictions.GetTypeRestriction( Expression, LimitType );
}
protected virtual DynamicMetaObject AddTypeRestrictions( DynamicMetaObject result ) {
BindingRestrictions typeRestrictions = GetTypeRestriction().Merge( result.Restrictions );
return new DynamicMetaObject( result.Expression, typeRestrictions, _metaObject.Value );
}
public override DynamicMetaObject BindInvokeMember( InvokeMemberBinder binder, DynamicMetaObject[] args ) {
DynamicMetaObject errorSuggestion = AddTypeRestrictions( _metaObject.BindInvokeMember( binder, args ) );
return binder.FallbackInvokeMember( _baseMetaObject, args, errorSuggestion );
}
}
You may be thinking, ok, this is cool, but what use it is it? What is the use case? First, it’s cool. Second, it sets the foundation for .NET mixins. Third, it gives us a second form of inheritance (after parasitic) for PowerShell.
What if we take the prototype and make it a collection of prototypes? What if instead of inheriting from DelegatingPrototype
we reuse the internal prototypal skeleton? If this sounds familiar, it should. I am describing ruby classes with modules and a base class, but with C#…
If you want to see more or play around with the code, you can find full implementations in the Archetype project.