A Simple DLR Binder

A lot of people find it a little confusing to write their first binder to consume dynamic objects running on the DLR. It’s not actually super simple but it’s not too hard either. To shine some light on this I thought I’d post a very simple binder that shows how you plug in. This example just supports adding 2 ints. If the target object is an IDynamicObject the object will get the first crack at implementing the operation. So for example a class written in IronPython which implements __iadd__ will call the __iadd__ method to do the addition (ok, that’ll be true once I support the current DLR way of doing operations, we’re still using an older version). For objects of any other types which don’t implement IDynamicObject it will report an error indicating the types and the operation kind. Even if you specify a different operation kind it will still just add ints.

There’s a few key pieces you need to understand here. First you’re returning an expression tree back to the DLR to tell the DLR what to do. The DLR will compile this expression tree and run it. You’re also returning a set of “restrictions”. These will be evaluated by the DLR to see if it can re-use the same code for other objects in the future. You’re unlimited in what you can do with restrictions as they can be arbitrary Expression trees. But here I’m simply restricting based upon the arguments .NET types which is one of the most common restrictions. Both the expression tree and restrictions get packaged up in a DynamicMetaObject which is conveniently the same type of object you receive as your arguments.

Finally there’s the hash identity which is how the DLR knows if it can share rules or not amongst different binders. The base DLR binders override GetHashCode/Equals to hash on the operation, member name, etc… depending on the binder type so I just return this here.

So here it is a simple binder. This targets the latest and greatest DLR sources which has experienced some renames and so isn’t compatible with IronPython 2.0. But you can always get the latest IronPython source code which this will compile against at https://www.codeplex.com/IronPython/SourceControl/ListDownloadableCommits.aspx

    class MyOperationBinder : BinaryOperationBinder {

        public override DynamicMetaObject FallbackBinaryOperation(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion) {

            if (target.LimitType == typeof(int) && arg.LimitType == typeof(int)) {

                return new DynamicMetaObject(

                    Expression.Add(

                        Expression.Convert(target.Expression, typeof(int)),

                        Expression.Convert(arg.Expression, typeof(int))

                    ),

                    BindingRestrictions.GetTypeRestriction(target.Expression, typeof(int)).Merge(BindingRestrictions.GetTypeRestriction(arg.Expression, typeof(int)))

                );

            }

           

            return new DynamicMetaObject(

                Expression.Throw(

                    Expression.New(

                        typeof(Exception).GetConstructor(new Type[] { typeof(string) }),

                        Expression.Constant(

                            String.Format("Can't perform operation {0} on {1} and {2}", this.Operation, target.LimitType, arg.LimitType)

                        )

                    )

                ),

                BindingRestrictions.GetTypeRestriction(target.Expression, typeof(int)).Merge(BindingRestrictions.GetTypeRestriction(arg.Expression, typeof(int)))

            );

        }

        public override object CacheIdentity {

            get { return this; }

        }

    }