Forwarding meta-object

The Dynamic Language Runtime (DLR) allows us to add dynamic behavior to .NET classes. Writing your own dynamic class, i.e. a class implementing IDynamicMetaObjectProvider interface, might be as easy as inheriting from System.Dynamic.DynamicObject. Or you might need more control over the dynamic operations and implement the IDynamicMetaObjectProvider interface manually. Or maybe you already have an existing class and need to add dynamic behavior while preserving its parent class. And perhaps you already have a class that implements IDynamicMetaObjectProvider, find this implementation useful and want to reuse it. What code do we need to write to achieve that?

The ForwardingMetaObject class defined below makes it easy to forward dynamic operations from one dynamic class (forwarder) to another (forwardee).

public sealed class ForwardingMetaObject : DynamicMetaObject {
    private readonly DynamicMetaObject _metaForwardee;

    public ForwardingMetaObject(Expression expression, BindingRestrictions restrictions, object forwarder, 
        IDynamicMetaObjectProvider forwardee, Func<Expression, Expression> forwardeeGetter)
        : base(expression, restrictions, forwarder) { 
       
        // We'll use forwardee's meta-object to bind dynamic operations.
        _metaForwardee = forwardee.GetMetaObject(
            forwardeeGetter(
                Expression.Convert(expression, forwarder.GetType())   // [1]
            )
        );      
    }

    // Restricts the target object's type to TForwarder. 
    // The meta-object we are forwarding to assumes that it gets an instance of TForwarder (see [1]). 
    // We need to ensure that the assumption holds.
    private DynamicMetaObject AddRestrictions(DynamicMetaObject result) {
        return new DynamicMetaObject(
           result.Expression,
           BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions),
           _metaForwardee.Value
       );
    }

    // Forward all dynamic operations or some of them as needed //

    public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
        return AddRestrictions(_metaForwardee.BindGetMember(binder));
    }

    public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
        return AddRestrictions(_metaForwardee.BindInvokeMember(binder, args));
    }

    public override DynamicMetaObject BindConvert(ConvertBinder binder) {
        return AddRestrictions(_metaForwardee.BindConvert(binder));
    }

    // ... //
}

Let’s use this class in an example. Let’s define some class B simply as a subclass of DynamicObject that supports GetMember, InvokeMember and Convert to String dynamic operations:

public class B : DynamicObject {
    private readonly string _name;

    public B(string name) {
        _name = name;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        result = "got member " + _name + "." + binder.Name;
        return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
        result = "invoked member " + _name + "." + binder.Name;
        return true;
    }

    public override bool TryConvert(ConvertBinder binder, out object result) {
        if (binder.Type == typeof(string)) {
            result = _name;
            return true;
        } else {
            result = null;
            return false;
        }
    }
}

And now let’s define a class A that forwards these operations to B via ForwardingMetaObject:

public class A : IDynamicMetaObjectProvider {
    private readonly B _b;
    public B B { get { return _b; } }

    public A(B b) {
        _b = b;
    }

    DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
        return new ForwardingMetaObject(parameter, BindingRestrictions.Empty, this, _b, 
            // B's meta-object needs to know where to find the instance of B it is operating on.
            // Assuming that an instance of A is passed to the 'parameter' expression
            // we get the corresponding instance of B by reading the "B" property.
            exprA => Expression.Property(exprA, "B")
        );
    }
}

Finally, let’s run some C# 4.0 code that shows how it all works together:

class Program {
    static void Main(string[] args) {
        B b1 = new B("b1");
        B b2 = new B("b2");

        dynamic a1 = new A(b1);
        dynamic a2 = new A(b2);

        Console.WriteLine(a1.SomeMember);
        Console.WriteLine(a2.SomeMember);
        Console.WriteLine(a1.SomeMethodCall());
        Console.WriteLine(a1.OtherMethodCall());
        Console.WriteLine((string)a1);
    }
}

Output:

got member b1.SomeMember
got member b2.SomeMember
invoked member b1.SomeMethodCall
invoked member b1.OtherMethodCall
b1

The really great thing about DLR is that your dynamic objects will also work in IronRuby and IronPython even though you haven’t thought about it at all when authoring them. Assuming that we compiled the above code into MyDynamicLibrary.dll we can run IronRuby REPL and check it out:

IronRuby 0.9.1.0 on .NET 4.0.20915.0
Copyright (c) Microsoft Corporation. All rights reserved.

>>> require 'MyDynamicLibrary.dll'
=> true
>>> b1, b2 = B.new('[b1 in Ruby]'), B.new('[b2 in Ruby]')
=> [B, B]
>>> a1, a2 = A.new(b1), A.new(b2)
=> [B, B]
>>> a1.SomeMethodCall
=> 'invoked member [b1 in Ruby].SomeMethodCall'
>>> a2.OtherMethodCall
=> 'invoked member [b2 in Ruby].OtherMethodCall'
>>> a1.to_s                             # invokes ToString
=> "B"                                   
>>> a1.to_str                           # invokes dynamic to string conversion
=> "[b1 in Ruby]"