我遇到了类型推断和C#编译器的问题.阅读了这个问题和这个问题后,我想我明白为什么它不起作用:我想知道的是,如果有任何方法可以解决问题以获得我喜欢的调用语义.
这里有一些代码说明了我的问题(对不起长度,这是我可以减少它的最短时间):
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
基本上我想让存储库只是通过调用ExecuteAsync方法并传递查询来执行查询.为了完成这项工作,我必须使用dynamic在运行时解析查询处理程序类型ExecuteQueryDynamicallyAsync.或者,我可以在调用方法时将查询类型和结果类型都指定为类型参数(按照ExecuteQueryStaticallyAsync),但这显然是一个冗长的调用,特别是当查询和/或返回类型具有长名称时.
我可以让编译器使用这样的方法签名来推断这两种类型:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored)
where TQuery : class, IQuery<TResult>
Run Code Online (Sandbox Code Playgroud)
这允许我做这样的事情:
var query = new MeaningOfLifeQuery();
...
... ExecuteQueryAsync(query, default(int)) ...
Run Code Online (Sandbox Code Playgroud)
但是我必须在每次调用时将一个虚拟值(被忽略)传递给方法.将签名更改为:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored = default(TResult))
where TQuery : class, IQuery<TResult>
Run Code Online (Sandbox Code Playgroud)
尝试避免在调用时传递值不起作用,因为编译器不能再推断结果类型(即返回到第一个).
关于如何解决这个问题,或者即使它是可以解决的,我有点不知所措.我唯一的选择似乎是坚持简单的调用语义(但必须依赖dynamic)或以某种方式改变我的架构.有什么方法可以得到我想要的调用语义吗?