Implementing double dispatch using dynamic
:
public interface IDomainEvent {}
public class DomainEventDispatcher
{
private readonly List<Delegate> subscribers = new List<Delegate>();
public void Subscribe<TEvent>(Action<TEvent> subscriber) where TEvent : IDomainEvent
{
subscribers.Add(subscriber);
}
public void Publish<TEvent>(TEvent domainEvent) where TEvent : IDomainEvent
{
foreach (Action<TEvent> subscriber in subscribers.OfType<Action<TEvent>>())
{
subscriber(domainEvent);
}
}
public void PublishQueue(IEnumerable<IDomainEvent> domainEvents)
{
foreach (IDomainEvent domainEvent in domainEvents)
{
// Force double dispatch - bind to runtime type.
Publish(domainEvent as dynamic);
}
}
}
public class ProcessCompleted : IDomainEvent { public string Name { get; set; } }
Works in most cases:
var dispatcher = new DomainEventDispatcher();
dispatcher.Subscribe((ProcessCompleted e) => Console.WriteLine("Completed " + e.Name));
dispatcher.PublishQueue(new [] { new ProcessCompleted { Name = "one" },
new ProcessCompleted { Name = "two" } });
Completed one
Completed two
But if the subclasses are not visible to the dispatch code, this results in a runtime error:
public static class Bomb
{
public static void Subscribe(DomainEventDispatcher dispatcher)
{
dispatcher.Subscribe((Exploded e) => Console.WriteLine("Bomb exploded"));
}
public static IDomainEvent GetEvent()
{
return new Exploded();
}
private class Exploded : IDomainEvent {}
}
// ...
Bomb.Subscribe(dispatcher); // no error here
// elsewhere, much later...
dispatcher.PublishQueue(new [] { Bomb.GetEvent() }); // exception
RuntimeBinderException
The type 'object' cannot be used as type parameter 'TEvent' in the generic type or method 'DomainEventDispatcher.Publish(TEvent)'
This is a contrived example; a more realistic one would be an event that is internal to another assembly.
How can I prevent this runtime exception? If that isn't feasible, how can I detect this case in the Subscribe
method and fail fast?
Edit: Solutions that eliminate the dynamic cast are acceptable, so long as they do not require a Visitor-style class that knows about all of the subclasses.
All you have to do is change your Publish method to:
Update
You also have to change the call to
This way you don't have to change Publish's signature
I prefer my other answer though: C# subscribe to events based on parameter type?
Update 2
About your question
Keep in mind that dynamic is not a special type.
Basically the compiler:
1)Replaces it with object
2)Refactors you code to more complicated code
3)Removes compile time checks (these checks are done in runtime )
If you try to replace
with
You will get the same message ,but this time in compile time. The error message is self explanatory:
As a final note.
dynamic was designed for specific scenarios,99,9% of the time you don't need it and you can replace it with statically typed code.
If you think you need it(like the above case) you are probably doing something wrong