C#.如何在对象内注入依赖项的多个实例

本文关键字:实例 依赖 注入 对象 | 更新日期: 2023-09-27 18:28:38

我有以下类(部分):

class SearchViewModel : BaseViewModel<SearchResultItem>
{        
    private readonly IDownloader _downloader;        
    public SearchViewModel( IDownloader downloader)
        : base(model)
    {
        _downloader = downloader;
    }
    private void Download(object sender, DoWorkEventArgs e)
    {
        _downloader.Download(item);
    }
}

我为IDownloader使用了构造函数注入,在多线程进入我的生活之前,它工作得很好。

_downloader有一个状态,我需要在单独的线程中运行_downloader.Download(项)(用户单击搜索结果页面上的下载按钮)。

目标:_downloader.Download(item)之前,应该初始化_downloader的新实例。我可以使用_container.Resolve(IDownloader),但它会破坏CompositionRoot原则。

我提出这个问题是为了讨论最佳解决方案,因为我认为直接初始化(new())或引用容器不是答案。

C#.如何在对象内注入依赖项的多个实例

为什么不直接对工厂进行手工操作?这是一种非常常见的依赖注入代码模式。

interface IDownloaderFactory 
{
  IDownloader Create();
}
class DownloaderFactory : IDownloaderFactory 
{
  IDownloader Create()
  {
    // either new it up here, or resolve from the container as you wish.
  }
} 

然后将该工厂注入到原始对象中。

class SearchViewModel : BaseViewModel<SearchResultItem>
{        
    private readonly IDownloaderFactory _factory;        
    public SearchViewModel( IDownloaderFactory factory)
        : base(model)
    {
        _factory = factory;
    }
    private void Download(object sender, DoWorkEventArgs e) 
    {
        _factory.Create().Download(item);
    }
}

这样,您就不依赖于特定于IOC容器的功能。

实现显然取决于您使用的容器,但如果我需要在Autofac中做类似的事情,我可能会这样做:

public SearchViewModel(Func<Owned<IDownloader>> downloaderFactory) 
    : base(model) 
{ 
    _downloaderFactory = downloaderFactory; 
} 
private void Download(object sender, DoWorkEventArgs e)  
{  
    _downloaderFactory().Value.Download(item);  
}  

Owned<T>是一个Autofac类,它表示一个不属于容器的T实例-解析它的类负责处理它。我不能100%确定Autofac是否会在每次调用Func<IDownloader>时向我返回一个新的IDownloader实例,所以我会使用Owned<T>来确定。

有些IoC容器没有所有权/生存期跟踪的概念,因此对Func<IDownloader>进行依赖就足够了——每次都可以保证获得一个新实例。

我不确定我是否完全理解这个问题,但如果你只想每个线程有一个新实例,你通常可以绑定指定的实例。例如,在Ninject中,您可以指定

.InThreadScope()

在绑定结束时。

更新

您没有提供如何进行基础绑定的详细信息。但在Ninject中,让我们假设您每次都想将IDownloader绑定到MyDownloader,但您希望每个线程都有相同的MyDownloader实例,您可以使用

Bind<IDownloader>.To<MyDownloader>().InThreadScope();

类似于Matt Hamilton的解决方案,但适用于Microsoft的DI容器。

定义Func<IDownloader>构造函数参数:

public SearchViewModel(Func<IDownloader> resolveDownloader) 
    : base(model) 
{ 
    _resolveDownloader = resolveDownloader; 
} 

要知道,MicrosoftDI容器不知道该为这样的参数传递什么。所以你需要像这样注册SearchViewModel

serviceCollection.AddTransient<SearchViewModel>(s => 
    new SearchViewModel(() => s.GetRequiredService<IDownloader>()));