嘿,我尝试将我的对象转换为该对象应该实现的接口。但是在执行代码期间,我遇到以下错误:
无法将 GetTreeHandler 类型的对象强制转换为 IQueryHandler、IQuery
。
为什么?一切似乎都很好。
不使用动态的任何想法?
查询调度程序
public async Task<TResult> ExecuteAsync<TResult>(IQuery<TResult> query)
{
if (query == null)
{
throw new ArgumentNullException("Query can not be null.");
}
var handlerType = typeof (IQueryHandler<,>).MakeGenericType(query.GetType(), typeof (TResult));
var handler = _context.Resolve(handlerType);
IQueryHandler<IQuery<TResult>, TResult> castedHandler = (IQueryHandler<IQuery<TResult>, TResult>) handler;
return (TResult) await castedHandler.ExecuteAsync(query);
}
获取树处理程序
public class GetTreeHandler : IQueryHandler<GetTree, Tree>
{
private readonly ProductContext _context;
public string Name { get; set; }
public GetTreeHandler(ProductContext context)
{
_context = context;
}
public async Task<Tree> ExecuteAsync(GetTree query)
=> await Task.FromResult(
_context.Trees.FirstOrDefault(x => x.Id == query.Id)
);
}
编辑
我当前的解决方案(部分有效):
public async Task<TResult> ExecuteAsync<TResult>(IQuery query) where TResult : class
{
if (query == null)
{
throw new ArgumentNullException("Query can not be null.");
}
var handlerType = typeof (IQueryHandler<,>).MakeGenericType(query.GetType(), typeof (TResult));
var handler = _context.Resolve(handlerType);
return await (Task<TResult>)
handler.GetType()
.GetMethod("ExecuteAsync")
.Invoke(handler, new object[]{query});
}
为什么是部分? 例如。处理器:
不起作用:
public class GetIndexesHandler : IQueryHandler<GetIndexes, IEnumerable<AssocIndexDto>>
工程:
public class GetIndexesHandler : IQueryHandler<GetIndexes,AssocIndexDto>
如您所见,这两个类实现相同的接口,但具有不同的第二个参数类型。
问题是 Resolve 方法找不到已注册的服务,但是如果我尝试调试此代码,我可以看到想要的服务已正确注册,但找不到。
你知道怎么解决吗?
您正在尝试将IQueryHandler<GetTree, Tree>
转换为IQueryHandler<IQuery<Tree>, Tree>
,但这仅在IQueryHandler<TQuery, TResult>
被指定为:
interface IQueryHandler<out TQuery, TResult>
请注意此处的out
参数。
out
关键字将TQuery
标记为输出参数。如果泛型类型参数被标记为输出参数,它允许我们将接口转换为更泛型的参数。例如,可以将IEnumerable<string>
转换为IEnumerable<object>
,因为IEnumerable<string>
将返回字符串,并且可以将它们表示为对象。
然而,对于in
论点,情况正好相反。在查看意图Action<in T>
时,我们可以向Action<SubType>
投Action<BaseType>
,因为该动作也始终能够处理SubType
。另一方面,不可能将Action<BaseType>
转换为Action<object>
,因为这将允许我们也将string
传递给操作(因为string
是一个object
),但这显然会在运行时失败。
因此,只有当TQuery
标有out
关键字时,才有可能将IQueryHandler<GetTree, Tree>
转换为IQueryHandler<IQuery<Tree>, Tree>
,但显然TQuery
是输入参数。因此,CLR 禁止转换,因为它可能导致运行时出错。
但是,在您的情况下,此问题将不存在,因为您知道传入的类型始终适合。但请记住,CLR 无法检查这一点,因此会阻止转换。
正如我在博客上描述的那样,一种解决方案是使用dynamic
关键字,如下所示:
public async Task<TResult> ExecuteAsync<TResult>(IQuery<TResult> query)
{
var handlerType =
typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _context.Resolve(handlerType);
return (TResult)await castedHandler.ExecuteAsync((dynamic)query);
}
另一种选择是指定并注册一个泛型包装器类型,该类型实现缺少TQuery
泛型类型的接口。这样,您可以完全避免使用dynamic
:
public interface IWrapper<TResult>
{
Task<TResult> ExecuteAsync(IQuery<TResult> query);
}
public async Task<TResult> ExecuteAsync<TResult>(IQuery<TResult> query)
{
var wrapperType =
typeof(Wrapper<,>).MakeGenericType(query.GetType(), typeof(TResult));
var wrapper = (IWrapper<TResult>)_context.Resolve(wrapperType);
return wrapper.ExecuteAsync(query);
}
// Don't forget to register this type by itself in Autofac.
public class Wrapper<TQuery, TResult> : IWrapper<TResult>
{
private readonly IQueryHandler<TQuery, TResult> handler;
public Wrapper(IQueryHandler<TQuery, TResult> handler) { this.handler = handler; }
Task<TResult> IWrapper<TResult>.ExecuteAsync(IQuery<TResult> query) =>
this.handler.ExecuteAsync((TQuery)query);
}