4

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.

Enrico Massone
  • 6,464
  • 1
  • 28
  • 56
  • 1
    Why don't you use generics for your dispatcher? avoiding the need to use dynamic at all? – Martin Ernst May 17 '19 at 10:38
  • 1
    @MartinErnst because at compile time you don't know anything about the concrete type of event, the only thing you know is that it is an instance of an object implementing the interface IEvent. This means that you don't know how to call the generic method (what type do you use as a type argument for the method invocation ?) – Enrico Massone May 17 '19 at 17:38

1 Answers1

0

This doesn't deal with the issue with Castle interceptors / dynamic, but I have written similar code numerous times without needing dynamic at all, by using a generic version of Dispatch.

eg:

public interface IEventHandlerFactory 
{
  IEventHandler<TEvent>[] GetHandlers<TEvent>(TEvent event) where TEvent : IEvent;
}   

public class EventDispatcher 
{
  private readonly IEventHandlerFactory factory;

  public EventDispatcher(IEventHandlerFactory factory)
  {
    this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
  }

  public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent
  {
    foreach(var handler in this.factory.GetHandlers(@event))
    {
      handler.Handle(@event);
    }
  }
}

calling it:

eventDispatcher.Dispatch(new MyCustomEvent());

If you do need to invoke it dynamically, you can still use reflection to call eventDispatcher.Dispatch but you wouldn't run into the issues with the cast to dynamic and Castle Interceptors - it's a possible workaround for what seems like a potential bug to me

Martin Ernst
  • 5,629
  • 2
  • 17
  • 14
  • 1
    This only works if the client code uses an explicit type binding (`.Dispatch( new Foo() )`. On the other hand, if they have a generic facade (e.g. where the only type constraint over the argument is `IEvent`), the concrete type information would still be unavailable. – Wiktor Zychla May 20 '19 at 11:17
  • @WiktorZychla can you give me an example? – Martin Ernst May 20 '19 at 12:16
  • @MartinErnst the problem with the generic solution is the way we call the method `Dispatch` on the `eventDispatcher` object. The call is done in a piece of code where we have an object whose compile time type is `IEvent`. That way you are not able to call the generic method, because you don't know the type argument for the call itself. Also, you cannot leverage the type inference: calling the generic method using `IEvent` as the type argument is not correct, because the event handler classes handle specific event types – Enrico Massone May 20 '19 at 16:43
  • @MartinErnst of course there are possible workarounds for that. A possible solution is, for instance, invoke the generic method via reflection by using the runtime type of the event object. That way you don't need to know the type argument at compile time. Of course there are some problems: that's not type safe (indeed you have the same issue with the dynamic approach), it's not really quick (but you can improve it by caching the reflection results) and it's probably less readable than the solution using the dynamics. – Enrico Massone May 20 '19 at 16:49
  • @MartinErnst do you know a way to call a generic method without knowing the type argument at compile time, apart from the reflection based approach ? (I don't indeed) – Enrico Massone May 20 '19 at 16:52
  • @WiktorZychla do you know a way to call a generic method without knowing the type argument at compile time, apart from the reflection based approach (I don't indeed) ? – Enrico Massone May 20 '19 at 16:52
  • Whenever I've done it, and in the libraries that I've looked at that do it, they all use reflection.. – Martin Ernst May 20 '19 at 17:24
  • You could make a non-generic Dispatch that just calls the generic version using dynamic - something like ((dynamic)this).Dispatch(@event) - at least that would circumvent the problem with interceptors (unless you have interceptors on the dispatcher!) – Martin Ernst May 20 '19 at 17:27