I apologize in advance for the terminology overload involved in this question: the word dynamic in what follows will be used for both the C# dynamic typing feature and the Castle Windsor dynamic proxy feature.
We basically have a scenario where, at runtime, we must chose the proper event handler for an event object. The event handlers are provided by a factory object which, internally, uses a castle windsor dependency injection container to provide object instances and the input provided to the factory is an instance of a marker interface IEvent
.
Just to fix the idea these are the classes involved (this is a simplified scenario but the essence of the problem is maintained):
public interface IEvent {}
public class CustomerCreated : IEvent
{
public string CustomerName { get; set; }
}
public interface IEventHandler {}
public interface IEventHandler<T> : IEventHandler where T: IEvent
{
void Handle(T event);
}
public class CustomerService : IEventHandler<CustomerCreated>
{
public void Handle(CutomerCreated @event)
{
// handle the event in some way...
}
}
public interface IEventHandlerFactory
{
IEventHandler[] GetHandlers(IEvent event);
}
Here is the consuming code that gets an event, asks the factory to provide the handlers and executes all the provided handlers (again this is a simplified version, but the essence remains):
public class EventDispatcher
{
private readonly IEventHandlerFactory factory;
public EventDispatcher(IEventHandlerFactory factory)
{
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
public void Dispatch(IEvent @event)
{
foreach(var handler in this.factory.GetHandlers(@event))
{
((dynamic)handler).Handle((dynamic)@event);
}
}
}
This code had worked fine for years since we decided to create a Castle Windsor Interceptor in order to intercept all the calls to the method Handle
of the class CustomerService
so that we can write some logs each time the method is called.
Now the bad part of the whole story...
The newly added castle windsor interceptor has broken the runtime binding of the Handle
method on the dynamic object ((dynamic)handler)
showed inside the class EventDispatcher
.
The error reported is a RuntimeBinderException, which states that the call to the best overload for the CastleDynamicProxy_14.Handle
method contains some invalid arguments (the method actually chosen and reported in the exception message is the wrong one, because it accepts a different event type as parameter).
We carefully investigated the exception and it basically means that the runtime binding is chosing the wrong Handle
method of the CustomerService
class to bind the call to (the CustomerService
handles several events in our real code, so it has a lot of methods called Handle
and each one of them takes a different type of event as its sole parameter, according to the IEventHandler<T>
interface definition).
The strange thing is that the introduction of the castle windsor dynamic proxy object, which wraps the real object (the CustomerService
being intercepted by Castle), has broken the C# runtime binding only for some events, while for the others the class EventDispatcher
showed above works perfectly fine as before. There is no relevant difference between the broken and the working events: all of them are POCO classes implementing the marker interface IEvent
.
Did anyone had a similar issue with dynamic code ? Is there a way to get some sort of the verbose logging about the process done by the CLR when it performs the runtime binding process on dynamic objects ?
We are not able to reproduce the same issue in a minimal example outside our application so I would exclude bugs at the Castle Dynamic Proxy level or C# level.
I have the feeling that the issue only dependends on the way we registered services and interceptors in Castle Windsor.
The point is that we have dozens of interceptors in our code and they all work fine. Furthermore, why only one or two events are broken and the others work fine with the same event dispatcher code and the same interceptors registered ?
I'm stuck in the investigation at the moment, I have no more idea to understand what's going wrong.