views:

1092

answers:

2

Does anyone know of a way to intercept dynamic method calls (particularly those that are going to raise RuntimeBinderExceptions) with a RealProxy? I was hoping to catch the exception and implement 'method missing' on top of that, but it appears to be thrown before the interceptor gets a look-in.

My test just looks like:

dynamic hello = MethodMissingInterceptor<DynamicObject>.Create();
Assert.AreEqual("World", hello.World());

Where World isn't actually implemented on DynamicObject. The interceptor is pretty straightforward - I was hoping to check IMethodReturnMessage.Exception for RuntimeBinderException and forward on to something like:

public IMessage MethodMissing(IMethodCallMessage call)
{
    return new ReturnMessage(call.MethodBase.Name, new object[0], 0, call.LogicalCallContext, call);
}

Unfortunately, all I see in my interceptor are some calls to GetType, and not the non-existant World method.

Failing that - does anyone know if there's a DynamicProxy version running happily on .NET 4.0 yet that might have tackled the problem?

+10  A: 

I'll start with the long answer. Every bind of a dynamic operation in C# does approximately these three things in this order:

  1. Ask the object to bind itself if it implements IDynamicMetaObjectProvider or is a COM object, and if that fails, then...
  2. Bind the operation to an operation on a plain-old-clr-object using reflection, and if that fails, then...
  3. Return a DynamicMetaObject that represents a total failure to bind.

You're seeing the GetType calls because in step 2, the C# runtime binder is reflecting over you to try to figure out if you have a "World" method that is appropriate to call, and this is happening because the IDynamicMetaObjectProvider implementation of hello, if there is one, couldn't come up with anything special to do.

Unfortunately for you, by the time the RuntimeBinderException is thrown, we are no longer binding. The exception comes out of the execution phase of the dynamic operation, in response to the meta object returned due to step 3. The only opportunity for you to catch it is at the actual call site.

So that strategy isn't going to work out for you if you want to implement method_missing in C#. You do have some options though.

One easy option is to implement IDynamicMetaObjectProvider in your MethodMissingInterceptor, and defer to the IDMOP implementation of the wrapped object. In case of failure on the part of the inner IDMOP, you can bind to whatever you want (perhaps a call to a method_missing delegate stored in the interceptor). The downside here is that this only works for objects that are known to be dynamic objects, e.g. those that implement IDMOP to begin with. This is because you are basically inserting yourself between steps 1 and 2.

Another alternative I can think of is to implement IDynamicMetaObjectProvider, and in it, respond positively to every bind, returning a call to a method that (a) produces the same code the C# compiler would have produced to bind in the first place, and (b) catches RuntimeBinderException to call a method_missing method. The downside here is that it would be quite complicated--you'd need to generate arbitrary delegate types and the IL that uses them, against the public types in the C# runtime binder assembly which, frankly, are not meant for public consumption. But at least you'd get method missing against all operations.

I am sure there are other strategies I have not thought of, such as you seem to be hinting at about using remoting proxies. I can't imagine what they look like though and I can't say if they'd be successful.

The crux of the problem here is that C# 4.0 does not have a design that anticipates your desire to do this. Specifically, you cannot easily insert yourself between steps 2 and 3. That brings me to the short answer, which is sorry, C# 4.0 does not have method_missing.

Chris Burrows
Thanks for the excellent explanation, Chris - I've just started going through your series of C# 'dynamic' posts on your blog. :)For my purposes, your first solution sounds like it should work. I only really want to make these calls for builder-style objects and for some added fluency in testing APIs, I don't need to catch them on arbitrary objects.
Thom
@Chris - note also "just in case"'s question below; actually, I'd quite like to know that ;-p
Marc Gravell
A: 

Is there a way to just query the dynamic object for the presence of a member without actually calling it?

justin.m.chase