I'm playing with cqs a little bit and I'm trying to implement this in a class library (so there's no IOC, IServiceProvider, etc). Here is some code that I wrote:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
public class Query : IQuery<bool>
{
public int Value { get; set; }
}
public class QueryHandler : IQueryHandler<Query, bool>
{
public bool Handle(Query query)
{
return query.Value > 0;
}
}
public class Dispatcher
{
private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();
public Dispatcher()
{
handlers.Add(typeof(Query), new QueryHandler());
}
public T Dispatch<T>(IQuery<T> query)
{
IQueryHandler<IQuery<T>, T> queryHandler;
if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
((queryHandler = handler as IQueryHandler<IQuery<T>, T>) == null))
{
throw new Exception();
}
return queryHandler.Handle(query);
}
}
And this si how I am calling my code:
Query query = new Query();
Dispatcher dispatcher = new Dispatcher();
var result = dispatcher.Dispatch(query);
But the problem is that inside the dispatcher, I don't know why the variable handler cannot be casted as IQueryHandler<IQuery<T>,T>
. Here is some extra data:
PS: I know how to make this work(with dynamic), but I want to understand why THIS code isn't working.
This code does not work because IQueryHandler
is invariant on the TQuery
generic parameter. TQuery
needs to be covariant in order for handler
to be convertible to IQueryHandler<IQuery<T>, T>
, but that is impossible, as I will explain later. You could however, make TQuery
contravariant, which allows you to convert handler
to IQueryHandler<ASubclassOfQuery, T>
. TResult
can be covariant though. This is the code to do this:
public interface IQueryHandler<in TQuery, out TResult> where TQuery : IQuery<TResult>
See this page for more info about generic variances.
As for why handler
is not IQueryHandler<IQuery<T>, T>
, let's first suppose that it is, which means this code would compile:
IQueryHandler<IQuery<T>, T> q = handler;
q.Handle(new MyQuery<T>());
where MyQuery
is defined like this:
class MyQuery<T> : IQuery<T> {}
However, handler
is of runtime type QueryHandler
. QueryHandler.Handle
only handles Query
objects, not MyQuery<T>
objects! We have a contradiction, and hence our assumption that handler
is a IQueryHandler<IQuery<T>, T>
must be false.