我有以下代码:
public class TempForm : Form
{
private readonly IGoogleAuth _googleAuth;
private readonly IComAssistant _comAssistant;
public TempForm(IGoogleAuth googleAuth, IComAssistant comAssistant)
{
_googleAuth = googleAuth;
_comAssistant = comAssistant;
InitializeComponent();
}
private void ButtonClick(object sender, EventArgs e)
{
var excelThread = new Thread(() =>
{
//NEED NEW INSTANCE OF EXCEL_APP PER THREAD
using (IExcelApp excel = new ExcelApp(_comAssistant))
{
//Do stuff with excel.
excel.CreateWorkBook();
//...
}
});
excelThread.SetApartmentState(ApartmentState.STA);
excelThread.Start();
}
private void InitializeComponent()
{
//Initialize form components
}
}
我对IGoogleAuth
或IComAssistant
服务没有任何问题,因为它们在容器中注册为Singletone
,我在表单构造函数中注入它们。
但是在ButtonClick
方法中,每个新线程都需要ExcelApp
的新实例。
我能做到这一点:
using (ThreadScopedLifestyle.BeginScope(container)) {
var excel = container.GetInstance<IExcelApp>();
}
但是通过这种方式,我需要将container
中声明的Program.cs
传递给我的TempForm
表单。
是否有可能在不传递容器本身的情况下实现此类行为?
如果不是 - 在多个地方使用container
实例的最佳做法是什么。
我们需要将它作为单例,或者将它们放在自己的ServiceLocator
实现中吗?
感谢。
答案 0 :(得分:5)
是否有可能在不传递容器本身的情况下实现此类行为?
是的,这当然是可能的。诀窍是将这个逻辑从Form组件中提取到它自己的组件中。换句话说,您创建了Aggregate Service。例如:
public class TempForm : Form
{
private readonly IGoogleAuth _googleAuth;
private readonly IExcelExporter _exporter;
public TempForm(IGoogleAuth googleAuth, IExcelExporter exporter)
{
_googleAuth = googleAuth;
_exporter = exporter;
InitializeComponent();
}
private void ButtonClick(object sender, EventArgs e)
{
_exporter.Export(...);
}
private void InitializeComponent()
{
//Initialize form components
}
}
这里我们将与生成excel文档相关的所有代码从Form中提取到自己的组件中。
此类实施可能如下所示:
public class ExcelExporter : IExcelExporter
{
private readonly IComAssistant _comAssistant;
public ExcelExporter(IComAssistant comAssistant)
{
_comAssistant = comAssistant;
}
private void Export(...)
{
//NEED NEW INSTANCE OF EXCEL_APP PER THREAD
using (IExcelApp excel = new ExcelApp(_comAssistant))
{
//Do stuff with excel.
excel.CreateWorkBook();
//...
}
}
}
注意这个组件本身没有线程概念。线程是该组件不应负责的问题。将此课程排除在本课程之外,使课程更容易理解,更容易测试。
但这确实意味着我们必须在某处实现这种线程逻辑。但是,我们希望将其从表单中删除ExcelExporter
。在执行此操作时,我们需要引用Container
。
需要访问Container
的每段代码都应集中在应用程序的启动代码中,例如Composition Root。
将此线程行为添加到新ExcelExporter
组件的有效方法是在IExcelExporter
周围使用代理:
public class BackgroundExcelExporterProxy : IExcelExporter
{
private readonly Container _container;
private readonly Func<IExcelExporter> _excelExporterFactory;
public ExcelExporter(
Container container, Func<IExcelExporter> excelExporterFactory)
{
_container = container;;
_excelExporterFactory = excelExporterFactory;
}
private void Export(...)
{
var excelThread = new Thread(() =>
{
using (ThreadScopedLifestyle.BeginScope(container))
{
var exporter = _excelExporterFactory();
exporter.Export(...);
}
});
excelThread.SetApartmentState(ApartmentState.STA);
excelThread.Start();
}
}
此类依赖于Container
。调用Export
时,它将启动一个新的Thread
,并在该线程中启动一个新的线程范围。在该线程范围内,它将解析具有依赖关系的新IExporter
。
当使用RegisterDecorator
方法在Simple Injector中注册此类时(就Simple Injector而言,这是一个装饰器),Simple Injector将原生地理解Func<IExcelExporter>
依赖关系,并将理解此委托应该解析装饰实例的实例(在您的情况下为ExcelExporter
)。
我们可以注册如下:
container.Register<IExcelExporter, ExcelExporter>();
container.RegisterDecorator<IExcelExporter, BackgroundExcelExporterProxy>(
Lifestyle.Singleton);
这将产生以下对象图:
new TempForm(
MyGoogleAuth(...),
new BackgroundExcelExporterProxy(
container,
() => new ExcelExporter(new MyComAssistant(...))));
我们需要将它设为单例,还是将它们放在自己的ServiceLocator实现中?
您可能认为BackgroundExcelExporterProxy
有一个服务定位器,但只要此类位于 组合根内,它就不是服务定位器,如here所述。