I'm having an issue with type inference and the C# compiler. Having read this question and this question I think I understand why it isn't working: what I would like to know is if there is any way I can work-around the problem in order to get my preferred calling semantics.
Here is some code that illustrates my problem (sorry for the length, this is the shortest I could reduce it to):
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace StackOverflow
{
interface IQuery<TResult> { }
class MeaningOfLifeQuery : IQuery<int> { }
interface IQueryHandler<TQuery, TResult> where TQuery : class, IQuery<TResult>
{
Task<TResult> ExecuteAsync(TQuery query);
}
class MeaningOfLifeQueryHandler : IQueryHandler<MeaningOfLifeQuery, int>
{
public Task<int> ExecuteAsync(MeaningOfLifeQuery query)
{
return Task.FromResult(42);
}
}
interface IRepository
{
Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query);
Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>;
}
class Repository : IRepository
{
public Repository(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
readonly IServiceProvider _serviceProvider;
public async Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query)
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync((dynamic) query);
}
public async Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult));
var handler = (IQueryHandler<TQuery, TResult>) _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync(query);
}
}
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddTransient<IQueryHandler<MeaningOfLifeQuery, int>, MeaningOfLifeQueryHandler>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var repository = new Repository(serviceProvider);
var query = new MeaningOfLifeQuery();
int result = repository.ExecuteQueryDynamicallyAsync(query).Result;
result = repository.ExecuteQueryStaticallyAsync<MeaningOfLifeQuery, int>(query).Result;
// result = repository.ExecuteQueryStaticallyAsync(query).Result;
// Doesn't work: type arguments cannot be inferred
}
}
}
Basically I want to get the repository to execute queries simply by calling an ExecuteAsync
method and passing the query. To make this work I have to use dynamic
to resolve the query handler type at runtime, as per ExecuteQueryDynamicallyAsync
. Alternatively, I can specify both the query type and result type as type parameters when calling the method (as per ExecuteQueryStaticallyAsync
) but this is obviously quite a wordy call, particularly when the query and/or return types have long names.
I can get the compiler to infer both types using a method signature like this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored)
where TQuery : class, IQuery<TResult>
which allows me to do something like this:
var query = new MeaningOfLifeQuery();
...
... ExecuteQueryAsync(query, default(int)) ...
but then I have to pass a dummy value (that gets ignored) to the method with each call. Changing the signature to this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored = default(TResult))
where TQuery : class, IQuery<TResult>
to try to avoid passing in the value when calling doesn't work, as the compiler can no longer infer the result type (ie. back to square one).
I'm at a bit of a loss as to how I can solve this problem, or even if it is solvable. My only options seem to be to stick with the simple calling semantics (but have to rely upon dynamic
) or to change my architecture in some way. Is there any way I can get the calling semantics I want?