I have always like Caliburn Micro since I started using few years back. Of course had the opportunity to use it at work as well and that made it more likeable. But at the same time, I am not a huge fan of one of the latest changes that happened in v4.0, even though I am not sure what could have been an ideal workaround.
Let me try to explain the minor issue and why the user of Caliburn Micro might have to live with it.
The Original Problem
In earlier versions of Caliburn Micro, the IEventAggregator
implementation (EventAggregator
) would search for implementation of IHandle
and its variants in the subscribers. Each time a message is published, the EventAggregator would walk through the current collection of Subscribers and filter out the ones which are interested in the particular message type.
To understand the problem a bit more clearly, let us look at the variants of IHandle
.
public interface IHandle { } public interface IHandle<T> { void Handle<T>(T message); } public interface IHandleWithTask<T> { Task Handle<T>(T message); }
The filtering process was based on the base interface of IHandle
and since the handlers were named Handle
in all the variants of interface, the identity of the Handler was detected as follows.
var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces .Where(x => typeof(IHandle).GetTypeInfo().IsAssignableFrom(x.GetTypeInfo()) && x.GetTypeInfo().IsGenericType); foreach(var @interface in interfaces) { var type = @interface.GetTypeInfo().GenericTypeArguments[0]; var method = @interface.GetRuntimeMethod("Handle", new Type[] { type }); if (method != null) { supportedHandlers[type] = method; } }
This as such, didn't have a problem. In fact, it did give Users a flexibility to choose between a Synchronous Handler (void Handle<T>(T message)
) and an asynchronous Handler (Task Handle<T>(T message)
) by choosing the appropriate interface. Remember, the handler themselves wasn't invoked asynchronously, but the point is the Handlers could now execute asynchronous code gracefully due to the return type.
However, the problem with having two explcit interfaces is that it gives the freedom of abuse of code by the developer. The developer could now choose to implement both variants of IHandle
interface now. This would result in a situation when the class could have two set of handlers.
public class VariableViewModel:PropertyChangedBase , IHandle<UserMessage>, IHandleWithTask<UserMessage>, { public void Handle(UserMessage message) { } Task IHandleWithTask<UserMessage>.Handle(UserMessage message) { } }
In this scenario, which of these Handlers would be invoked first ? This leaves a certain level of uncertainity. Also, having two handlers for a message type doesn't feel like the right way to go.
New Approach
With Caliburn Micro 4.x, things have changed slightly. Different variants of IHandle
has been removed and instead, a solitary IHandle<T>
has been retained. There is change in signature of method as well.
public interface IHandle<TMessage> { Task HandleAsync(TMessage message, CancellationToken cancellationToken); }
The handler method has been renamed as HandleAsync
and has a return type of Task
. The changes is reflected in the EVentAggregator impementation too
var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces .Where(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IHandle<>)); foreach (var @interface in interfaces) { var type = @interface.GetTypeInfo().GenericTypeArguments[0]; var method = @interface.GetRuntimeMethod("HandleAsync", new[] { type, typeof(CancellationToken) }); if (method != null) { _supportedHandlers[type] = method; } }
This eliminates the problems which was in the earlier system owing to the existing of multiple variants of interface. However, there is another problem that has come up, even though it is comparitively a lesser devil of the two.
The problem is the naming convention used. The method (the handler) would be named with a suffix async even in the case when it needn't be. You are pretty sure your style analysers would be shouting out loud complaining about the suffix.
I wonder if there was a way out - a middle path, which could do away with both set of problems. Perhaps in another post we could discuss the possible approaches to workaround this.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.