我希望DI按实际类型解析(无论是命令还是命令).如何度过这一难关?
在这种情况下,DI Container无法解决此问题,原因如下:
- 在编译时,您向
GetRequiredService<T>
方法提供了ICommandHandler<ICommand>
;它没有得到任何运行时信息,无法发现任何不同之处.
- 当被要求解析
ICommandHandler<ICommand>
时,容器不能返回任何其他内容,因为ICommandHandler<ICommand>
与ICommandHandler<CommandA>
是不同的类型.即使它可以,该请求甚至也是模棱两可的,因为它可能导致ICommandHandler<CommandA>
、or和ICommandHandler<CommandB>
.它应该返回哪一个?
- 在.NET中,不可能将
ICommandHandler<ICommand>
转换为ICommandHandler<CommandA>
,反之亦然,除非您等于ICommandHandler<T>
variant(即,您需要用in
或out
来标记T
).
这里的解决方案是求助于使用反射.例如:
public void HandleCommand(ICommand command)
{
Type handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
dynamic handler = _serviceProvider.GetRequiredService(handlerType);
handler.Handle((dynamic)command);
}
在本例中,为简单起见,我使用了关键字dynamic
.使用这个关键字有好处也有坏处.当然,明显的缺点是失go 了编译时支持.这并不是一个大问题,因为您通常在应用程序中只有一个地方使用反射调用命令处理程序,并且该代码可以很容易地进行(单元)测试.然而,更重要的缺点是,如果解析的处理程序实现是内部的,即使ICommandHandler<T>
被定义为public
,它也会失败.这是由C#编译器在幕后使用的C#运行时绑定器机制造成的.我曾多次与这种不幸的行为作斗争.
因此,您也可以使用反射API来调用该方法,而不是使用动态:
public void HandleCommand(ICommand command)
{
Type handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
object handler = _serviceProvider.GetRequiredService(handlerType);
MethodInfo method = handlerType.GetMethod("Handle");
try
{
method.Invoke(handler, new object[] { command });
}
catch (TargetInvocationException ex)
{
// When a Reflection Invoke call fails, the original exception
// is wrapped in a TargetInvocationException. This complicates
// error handling by consumers, which is why we 'unwrap' that
// exception and rethrow the original exception. The only
// to do this without losing the original stack trace is using
// The ExceptionDispatchInfo class.
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
}