我正在使用MEF为WinForms应用程序实现插件,我注意到一些我不理解的组合行为。
为了解决这个问题,我正在使用MEF和.NET 4。
作为一种tl; dr,这是我在容器和成分方面不明确的地方:
ComposeParts(this)
是该类的现有实例添加到容器中还是另一个实例创建了?GetExportedValue<T>()
方法时,返回的部分是否已经编组(或填充了导入部分),还是需要明确编写部分?请继续阅读,了解有关每个问题的详细信息......
关于第一个问题,我有一个主申请表,其中有一个面板,可以托管“普通”应用程序用户控件以及插件中的用户控件。这个主应用程序表单实现了一个名为IHostingForm
的接口,它允许它向插件公开它自己的一些功能。此主应用程序表单通过IHostingForm
接口导出自身,并且需要填充导入。这是一段代码:
[Export(typeof(IHostingForm))]
public partial class frmMain : RibbonForm, IHostingForm
{
//public/private form methods here...
private CompositionContainer _container;
//An import that needs to be filled.
[ImportMany]
public IEnumerable<Lazy<IAddInController, IAddInControllerMetadata>> AddInControllers;
public frmMain()
{
//Init code here omitted to save space...
//Setup the container
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
//Temporary location while testing...
catalog.Catalogs.Add(new DirectoryCatalog(Application.StartupPath));
_container = new CompositionContainer(catalog);
try
{
//Is the current instance of the class (this) added to the
//container or does the container construct its own instance?
_container.ComposeParts(this);
}
catch (CompositionException ex)
{
//Error catching code omitted...
}
}
//IHostingForm implementation
public void AddTabToTabBar(string tabText)
{
RibbonTab rt = new RibbonTab(tabText);
//Some more init code here which I'm leaving out to save space...
this.ribbonBar1.CommandTabs.Add(rt);
}
//etc...
}
请注意上面的代码段如何导出自身,但它也在构造函数中调用ComposeParts(this)
?这是否会创建表单的单独实例,或者ComposeParts(this)
是否足够聪明,以便知道使用现有实例并将其添加到容器中?
这引出了我的第二个问题。由于我不确定上面使用的组合方法是否正确,我尝试在应用程序的Program类中创建容器,我发现了另一种我不理解的行为。我将容器初始化代码从表单的类中移出,并转移到Program中,如下所示:
static class Program
{
internal static CompositionContainer _container;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Setup the container
var catalog = New AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
catalog.Catalogs.Add(new DirectoryCatalog(Application.StartupPath));
_container = new CompositionContainer(catalog);
IHostingForm frm = _container.GetExportedValue<IHostingForm>();
//If I don't call this, the form's imports don't get satisfied
_container.CompostParts(frm);
Application.Run((Form)frm);
}
}
根据MSDN和the answer to this question上的文档,我认为GetExportedValue<T>()
方法会实例化给定类型的导出并组成它(或满足其依赖性?),因为它是从容器里出来的。但是通过测试,似乎在从容器中获取零件后,我必须明确地告诉容器组成零件以便填充其进口。这似乎与我到目前为止所读到的内容相矛盾。
我不确定这是否是多个问题。如果他们需要分开,请告诉我,我会很乐意编辑这个问题并创建另一个问题。但是,对我来说,他们似乎有关系,因为他们都在处理我对容器和组合物发生的不了解。
答案 0 :(得分:1)
请注意上面的代码片段如何导出自身,但它也在构造函数中调用ComposeParts(this)?这是否会创建表单的单独实例,或者ComposeParts(这个)是否足够智能,以便知道使用现有实例并将其添加到容器中?
它足够“聪明”,它会按照它所说的去做。 ComposeParts(this)
将首先查看Import
需要满足哪些this
。然后它将尝试使用匹配的Export
查找类型/类型并调用其无参数构造函数,在创建实例后,它将查看该类型的Import
并重新开始。
现在,如果在某些时候遇到要求已创建的类型的Import
指令,它会将现有实例提供给该Import
指令 - 而不是创建第二个实例。
您可以通过向几种类型添加无参数构造函数并在其中放置断点来轻松验证这一点。
关于你的第二个问题,我建议提出一个单独的问题 - 但据我所知,ComposeParts
将找到或实例化以前未使用/未知的部分和宣传{{ 1}}调用它的类型/对象的属性 - 在您的情况下Export
。另一方面,[Export(typeof(IHostingForm))]
将仅搜索已存在的部分,不实例化新部分 - 这意味着如果GetExportedValue
要求部分X,但之前没有创建此部分Import
或ComposeParts
- SatisfyImports
后面的属性/字段将保持不满意/ Import
。