Previously I looked at forwarding property calls of an interface to those of an anonymous type instance. Now I'd like to see if we can forward method calls as well. But how? We can't create an anonymous type instance with methods, and we can't assign assign anonymous methods as properties. This doesn't work:
var duck = new { Color = "white", Quack = () => "Quack!" };
but this does:
[Test] public void CanInvokeMethod() { var duck = new { Color = "white", Quack = (Func<string>)(() => "Quack!") }; }
Let's modify our interceptor once again, this time to retrieve a delegate property named the same as the invoked method and invoke it.
internal class DuckTypingInterceptor : IInterceptor { [... snip ...] public void Intercept(IInvocation invocation) { [... snip ...] if (invocation.IsMethodCall()) { var method = GetMethod(invocation.Method.Name); invocation.ReturnValue = method.DynamicInvoke(invocation.Arguments); } } [... snip ...] private Delegate GetMethod(string name) { return anonymous.GetPropertyValue(name) as Delegate; } }
[Test] public void CanInvokeMethod() { var duck = new { Color = "white", Quack = (Func<string>)(() => "Quack!") }; var typedDuck = duck.As<IDuck>(); Assert.That("Quack!" == typedDuck.Quack()); }
It works! Now... what about overloads? This is a bit trickier. We can start by deciding that overloads will be stored as an array of System.Delegates.
[Test] public void CanInvokeOverloadedMethods() { var launchPad = new { Fly = new Delegate[] { (Func<Direction>)(() => Direction.West), (Func<Direction, Direction>)(d => d) } }; var launchPadMcQuack = launchPad.As<IDuck>(); Assert.That(Direction.West == launchPadMcQuack.Fly()); Assert.That(Direction.South == launchPadMcQuack.Fly(Direction.South)); }
A modified GetMethod method in our interceptor allows the test to pass.
private Delegate GetMethod(IInvocation invocation) { var methodProperty = anonymous.GetPropertyValue(invocation.Method.Name); if (methodProperty is Delegate) { return methodProperty as Delegate; } var overloads = methodProperty as Delegate[]; return overloads.Where( m => m.Method.GetParameters().Count() == invocation.Arguments.Count()).First(); }
This is obviously a naive implementation - overload resolution is extremely difficult. This will fail for overloads with the same number of parameters differentiated by type, methods with the params keyword, and methods with out/ref parameters. For now, though, we have something that will work well for very simple interfaces. In my final post, I'll try to make the overload resolution a little smarter and handle one other scenario I haven't talked about - indexed properties.
Comments