是否可以在简单注入器的方法中使用线程范围的生活方式
Is it possible to use Thread Scoped Lifestyle in method with Simple Injector
我有以下代码:
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>();
}
但是通过这种方式,我需要将在 Program.cs
中声明的 container
传递到我的 TempForm
表单中。
是否可以在不传递容器本身的情况下实现这种行为?
如果否 - 在多个地方使用 container
实例的最佳做法是什么。
我们需要将其作为单例,或者将它们放在自己的 ServiceLocator
实现中?
谢谢。
Is it possible to achive such behavior without passing container itself?
是的,这当然有可能。诀窍是将此逻辑从您的 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 文档相关的代码从表单中提取到它自己的组件中。
这样的实现可能如下所示:
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();
//...
}
}
}
注意这个组件本身没有线程的概念。线程是该组件不应该负责的问题。将此排除在 class 之外会使 class 更易于理解和测试。
但这确实意味着我们必须在某处实现此线程逻辑。然而,我们希望将其排除在表单和 ExcelExporter
之外。在执行此操作时,我们需要引用 Container
.
需要访问 Container
的每一段代码都应该集中在应用程序的启动代码 a.k.a 中。 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();
}
}
此 class 依赖于 Container
。当调用 Export
时,它将启动一个新的 Thread
并在该线程内启动一个新的线程范围。在该线程范围内,它将解析一个新的 IExporter
及其依赖项。
当这个class使用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(...))));
We need to make it as singleton, or put them in own ServiceLocator implementation?
您可能认为 BackgroundExcelExporterProxy
具有服务定位器,但只要此 class 驻留在 内部 组合根,它不是服务定位器,如 here 所述。
我有以下代码:
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>();
}
但是通过这种方式,我需要将在 Program.cs
中声明的 container
传递到我的 TempForm
表单中。
是否可以在不传递容器本身的情况下实现这种行为?
如果否 - 在多个地方使用 container
实例的最佳做法是什么。
我们需要将其作为单例,或者将它们放在自己的 ServiceLocator
实现中?
谢谢。
Is it possible to achive such behavior without passing container itself?
是的,这当然有可能。诀窍是将此逻辑从您的 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 文档相关的代码从表单中提取到它自己的组件中。
这样的实现可能如下所示:
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();
//...
}
}
}
注意这个组件本身没有线程的概念。线程是该组件不应该负责的问题。将此排除在 class 之外会使 class 更易于理解和测试。
但这确实意味着我们必须在某处实现此线程逻辑。然而,我们希望将其排除在表单和 ExcelExporter
之外。在执行此操作时,我们需要引用 Container
.
需要访问 Container
的每一段代码都应该集中在应用程序的启动代码 a.k.a 中。 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();
}
}
此 class 依赖于 Container
。当调用 Export
时,它将启动一个新的 Thread
并在该线程内启动一个新的线程范围。在该线程范围内,它将解析一个新的 IExporter
及其依赖项。
当这个class使用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(...))));
We need to make it as singleton, or put them in own ServiceLocator implementation?
您可能认为 BackgroundExcelExporterProxy
具有服务定位器,但只要此 class 驻留在 内部 组合根,它不是服务定位器,如 here 所述。