I was trying to run this code, but there is a problem that an object's type doesn't match with its abstract class type when I am trying to check
it's on the line if (handler is Handler<IRequest<T>, T> h)
void Main()
{
var requestHandler = new RequestHandler();
var result = requestHandler.Handle(new GetAge());
Console.WriteLine(result);
}
public interface IRequest<T> { }
public class GetAge : IRequest<int> { }
public interface IHandler {}
public abstract class Handler<TRequest, TResponse> : IHandler where TRequest : IRequest<TResponse>
{
public TResponse Handle(IRequest<TResponse> request)
{
return Handle((TRequest)request);
}
protected abstract TResponse Handle(TRequest requst);
}
public class GetAgeHandler : Handler<GetAge, int>
{
protected override int Handle(GetAge request)
{
return 20;
}
}
public class RequestHandler
{
public Dictionary<Type, IHandler> requestHandlers = new()
{
[typeof(GetAge)] = new GetAgeHandler()
};
public T Handle<T>(IRequest<T> request)
{
var handler = requestHandlers[request.GetType()];
if (handler is Handler<IRequest<T>, T> h)
{
return h.Handle(request);
}
return default;
}
}
It looks like it should print 20, but it doesn't.
Eventually I made it to work adding an extra Interface, and changing the Handle method in RequestHandler, but now the code became complicated.
Is there a way to make it simplier with using covariance or contravariance? I've tried using them, but couldn't achieve much.
As @Hayden commented, it should be possible, but how?
void Main()
{
var requestHandler = new RequestHandler();
var result1 = requestHandler.Handle(new GetAge());
var result2 = requestHandler.Handle(new GetName());
Console.WriteLine(result1);
Console.WriteLine(result2);
}
public interface IRequest<T> { }
public class GetAge : IRequest<int> { }
public class GetName : IRequest<string> { }
public interface IHandler { }
public interface IHandler<TResponse> : IHandler
{
public TResponse Handle(IRequest<TResponse> request);
}
public abstract class Handler<TRequest, TResponse> : IHandler<TResponse>
where TRequest : IRequest<TResponse>
{
public TResponse Handle(IRequest<TResponse> request)
{
return Handle((TRequest)request);
}
protected abstract TResponse Handle(TRequest requst);
}
public class GetAgeHandler : Handler<GetAge, int>
{
protected override int Handle(GetAge request)
{
return 20;
}
}
public class GetNameHandler : Handler<GetName, string>
{
protected override string Handle(GetName request)
{
return "Foo";
}
}
public class RequestHandler
{
public Dictionary<Type, IHandler> requestHandlers = new()
{
[typeof(GetAge)] = new GetAgeHandler(),
[typeof(GetName)] = new GetNameHandler()
};
public T Handle<T>(IRequest<T> request)
{
var handler = requestHandlers[request.GetType()];
if (handler is IHandler<T> h)
{
return h.Handle(request);
}
return default;
}
}
Simply put, the pattern matching fails due generic types being invariant by default. Even if you have
Handler<GetAge, int>, it is not considered to be compatible withHandler<IRequest<int>, int>. If the generic types were a covariance or contravariance, then this would be allowed, but in this specific case, it can't be.One way around this is to define the Handle method like so:
Which you would call it like: