让我们假设我们有以下代码

我们的命令都实现相同的接口

public interface ICommand {}

并且我们有实现以下接口的命令处理程序

public interface ICommandHandler<T> where T: ICommand {}

命令处理程序的实际实现注册到DI.

现在,如果我们有基于某些条件构建命令的方法

public ICommand BuildCommand()
{
   if(someCondition) return new CommandA();
   else return new CommandB();
}

我们在代码中使用它,如下所示

public class SomeClass
{
  IServiceProvider _serviceProvder; 
  public void method_1()
  {
    ICommand command = BuildCommand();
    HandleCommand(command);
  }

  public void HandleCommand<T>(T command) 
  {
    var handler = _serviceProvider.GetRequiredService<ICommandHandler<T>>();
    handler.Handle();
  }

If将抛出错误,声明它无法解析服务 ICommandHandler<ICommand>

我希望DI按实际类型解析(无论是命令还是命令).如何度过这一难关?

推荐答案

我希望DI按实际类型解析(无论是命令还是命令).如何度过这一难关?

在这种情况下,DI Container无法解决此问题,原因如下:

  • 在编译时,您向GetRequiredService<T>方法提供了ICommandHandler<ICommand>;它没有得到任何运行时信息,无法发现任何不同之处.
  • 当被要求解析ICommandHandler<ICommand>时,容器不能返回任何其他内容,因为ICommandHandler<ICommand>ICommandHandler<CommandA>是不同的类型.即使它可以,该请求甚至也是模棱两可的,因为它可能导致ICommandHandler<CommandA>orICommandHandler<CommandB>.它应该返回哪一个?
  • 在.NET中,不可能将ICommandHandler<ICommand>转换为ICommandHandler<CommandA>,反之亦然,除非您等于ICommandHandler<T> variant(即,您需要用inout来标记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();
    }
}

.net相关问答推荐

删除数据库项目中的表

Blazor服务器应用程序需要在页面上点击才能与元素交互

竖线在 PropertyGroup .csproj 文件中的含义

如何查询 DOTNET_CLI_TELEMETRY_OPTOUT 是否永久设置为 TRUE?

保存时不保留 XML 格式

仅在有换行符时捕获分隔符之间的所有文本

xunit Assert.ThrowsAsync() 不能正常工作?

StreamWriter.Flush() 和 StreamWriter.Close() 有什么区别?

lock() 是否保证按请求的顺序获得?

在生产中使用实体框架(代码优先)迁移

为什么 StyleCop 建议在方法或属性调用前加上this?

如何 Select 数据表中列的最小值和最大值?

我可以使用 UriTemplate 将非字符串传递给 WCF RESTful 服务吗?

.NET 世界是否有 Maven 替代方案或端口?

从 Web.Config 中的邮箱友好显示名称存储 Smtp

监听依赖属性的变化

DataGridView 在我的两个屏幕之一上的可怕重绘性能

静态方法继承的正确替代方法是什么?

如何从其十六进制 RGB 字符串创建 System.Drawing.Color?

为什么 !0 是 Microsoft 中间语言 (MSIL) 中的一种类型?