在托管扩展性框架(MEF)中, 编程模型 是定义 MEF 在其上运行的概念对象集的特定方法。 这些概念对象包括部件、导入和导出。 MEF 使用这些对象,但不指定应如何表示它们。 因此,可以实现各种编程模型,包括自定义编程模型。
MEF 中使用的默认编程模型是 特性化编程模型。 在特性化编程模型部件中,导入、导出和其他对象是使用修饰普通 .NET Framework 类的属性定义的。 本主题介绍如何使用特性化编程模型提供的属性来创建 MEF 应用程序。
导入和导出基础知识
导出是指一个部件提供给容器中其他部件的值,而导入是一个部件向容器提出的需求,由可用的导出进行满足。 在特性化编程模型中,通过使用 Import
和 Export
属性修饰类或成员来声明导入和导出。 特性 Export
可以修饰类、字段、属性或方法,而 Import
特性可以修饰字段、属性或构造函数参数。
为了使导入与导出匹配,导入和导出必须具有相同 的协定。 协定包含一个字符串,称为 协定名称,以及导出或导入的对象的类型,称为 协定类型。 只有在协定名称和协定类型均匹配时,才会认为导出能够满足特定导入。
协定参数可以是隐式或显式参数或两者。 以下代码显示了声明基本导入的类。
Public Class MyClass1
<Import()>
Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
[Import]
public IMyAddin MyAddin { get; set; }
}
在此导入中,该 Import
属性既没有协定类型,也没有附加协定名称参数。 因此,这两者均将从修饰的属性推断而出。 在这种情况下,协定类型将为 IMyAddin
,协定名称将是从协定类型创建的唯一字符串。 (换言之,协定名称将仅与其名称同样从类型 IMyAddin
推断而出的导出匹配。)
下面显示了与上一个导入匹配的导出。
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
在此导出中,合同类型是 IMyAddin
,因为它被指定为 Export
属性的参数。 导出的类型必须与协定类型相同、派生自协定类型,或者实现协定类型(如果它是接口)。 在此导出中,实际类型 MyLogger
实现接口 IMyAddin
。 协定名称是从协定类型推断的,这意味着此导出将与以前的导入匹配。
注释
通常应对公共类或成员声明导出和导入。 其他声明也受支持,但如果导出或导入私有成员、受保护成员或内部成员,将会损坏部件的隔离模型,因此建议不要这样做。
合同类型必须与导出和导入完全匹配才能被视为一致。 请看下面的导出。
<Export()> 'WILL NOT match the previous import!
Public Class MyLogger
Implements IMyAddin
End Class
[Export] //WILL NOT match the previous import!
public class MyLogger : IMyAddin { }
在此导出中,协定类型 MyLogger
不是 IMyAddin
。 尽管 MyLogger
实现了 IMyAddin
,因此可以强制转换为 IMyAddin
对象,但此导出不会与先前的导入匹配,因为合同类型不同。
一般情况下,不需要指定协定名称,并且大多数协定应根据协定类型和元数据进行定义。 但是,在某些情况下,必须直接指定合同名称。 最常见的情况是类导出多个共享通用类型的值,例如基元。 合同名称可以指定为Import
或Export
属性的第一个参数。 下面的代码演示具有指定协定名称 MajorRevision
的导入和导出。
Public Class MyExportClass
'This one will match
<Export("MajorRevision")>
Public ReadOnly Property MajorRevision As Integer
Get
Return 4
End Get
End Property
<Export("MinorRevision")>
Public ReadOnly Property MinorRevision As Integer
Get
Return 16
End Get
End Property
End Class
public class MyClass
{
[Import("MajorRevision")]
public int MajorRevision { get; set; }
}
public class MyExportClass
{
[Export("MajorRevision")] //This one will match.
public int MajorRevision = 4;
[Export("MinorRevision")]
public int MinorRevision = 16;
}
如果未指定协定类型,仍将从导入或导出的类型推断它。 但是,即使显式指定协定名称,协定类型也必须完全匹配,才能将导入和导出视为匹配项。 例如,如果字段是字符串,则推断的 MajorRevision
协定类型不匹配,并且导出与导入不匹配,尽管具有相同的协定名称。
方法的导入和导出
该 Export
特性还可以像类、属性或函数一样修饰方法。 方法导出必须指定协定类型或协定名称,因为无法推断该类型。 指定的类型可以是自定义委托或泛型类型,例如 Func
。 以下类导出一个名为DoSomething
的方法。
Public Class MyAddin
'Explicitly specifying a generic type
<Export(GetType(Func(Of Integer, String)))>
Public Function DoSomething(ByVal TheParam As Integer) As String
Return Nothing 'Function body goes here
End Function
End Class
public class MyAddin
{
//Explicitly specifying a generic type.
[Export(typeof(Func<int, string>))]
public string DoSomething(int TheParam);
}
在此类中, DoSomething
方法采用单个 int
参数,并返回 string
。 若要匹配此导出,导入部件必须声明适当的成员。 以下类导入 DoSomething
该方法。
Public Class MyClass1
'Contract name must match!
<Import()>
Public Property MajorRevision As Func(Of Integer, String)
End Class
public class MyClass
{
[Import] //Contract name must match!
public Func<int, string> DoSomething { get; set; }
}
有关如何使用该 Func<T, T>
对象的详细信息,请参阅 Func<T,TResult>。
导入类型
MEF 支持多种导入类型,包括动态导入、延迟导入、前置条件导入和可选导入。
动态导入
在某些情况下,导入类可能需要匹配具有特定协定名称的任何类型的导出。 在此方案中,类可以声明 动态导入。 下面的导入与具有协定名称“TheString”的任何导出匹配。
Public Class MyClass1
<Import("TheString")>
Public Property MyAddin
End Class
public class MyClass
{
[Import("TheString")]
public dynamic MyAddin { get; set; }
}
从 dynamic
关键字推断协定类型时,它将匹配任何协定类型。 在这种情况下,导入应 始终 指定要匹配的合同名称。 (如果未指定协定名称,则导入将被视为与不导出匹配。以下两个导出都将与以前的导入匹配。
<Export("TheString", GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
<Export("TheString")>
Public Class MyToolbar
End Class
[Export("TheString", typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
[Export("TheString")]
public class MyToolbar { }
显然,导入类必须准备好处理任意类型的对象。
延迟导入
在某些情况下,导入类可能需要间接引用导入的对象,以便不会立即实例化该对象。 在此方案中,类可以使用协定类型声明Lazy<T>
。 下面的导入属性声明一个延迟导入。
Public Class MyClass1
<Import()>
Public Property MyAddin As Lazy(Of IMyAddin)
End Class
public class MyClass
{
[Import]
public Lazy<IMyAddin> MyAddin { get; set; }
}
从组合引擎的角度来看,协定类型 Lazy<T>
被视为与协定类型的 T
相同。 因此,以前的导入将匹配以下导出。
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
合同名称和合同类型可以在 Import
属性中指定用于延迟导入,如前面提到的“基本导入和导出”部分所述。
必备导入
导出的 MEF 部件通常由组合引擎创建,以响应直接请求或填充匹配的导入项的需求。 默认情况下,创建部件时,合成引擎使用无参数构造函数。 若要使引擎使用不同的构造函数,可以使用属性标记它 ImportingConstructor
。
每个部件可能只有一个构造函数可供合成引擎使用。 提供无参数构造函数和无 ImportingConstructor
属性或提供多个 ImportingConstructor
属性将产生错误。
若要填充用 ImportingConstructor
属性标记的构造函数的参数,所有这些参数都将自动声明为导入。 这是一种声明在部件初始化过程中使用的导入的简便方法。 以下类使用 ImportingConstructor
来声明导入。
Public Class MyClass1
Private _theAddin As IMyAddin
'Parameterless constructor will NOT be used
'because the ImportingConstructor
'attribute is present.
Public Sub New()
End Sub
'This constructor will be used.
'An import with contract type IMyAddin
'is declared automatically.
<ImportingConstructor()>
Public Sub New(ByVal MyAddin As IMyAddin)
_theAddin = MyAddin
End Sub
End Class
public class MyClass
{
private IMyAddin _theAddin;
//Parameterless constructor will NOT be
//used because the ImportingConstructor
//attribute is present.
public MyClass() { }
//This constructor will be used.
//An import with contract type IMyAddin is
//declared automatically.
[ImportingConstructor]
public MyClass(IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
}
默认情况下,该 ImportingConstructor
属性对所有参数导入使用推断的协定类型和协定名称。 可以通过使用 Import
属性修饰参数来替代此参数,这些参数随后可以显式定义协定类型和协定名称。 以下代码演示使用此语法导入派生类而不是父类的构造函数。
<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)
End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
在使用集合参数时应特别小心。 例如,如果在具有ImportingConstructor
类型参数的构造函数上指定IEnumerable<int>
,则导入会匹配类型IEnumerable<int>
的单个导出,而不是一组类型int
的导出。 要匹配一组类型为int
的导出,您必须用ImportMany
特性修饰参数。
属性 ImportingConstructor
声明为导入的参数也标记为 先决条件导入。 MEF 通常允许导出和导入形成 循环。 例如,循环是对象 A 导入对象 B 的位置,后者又导入对象 A。在一般情况下,循环不是问题,合成容器通常构造这两个对象。
当部件的构造函数需要导入的值时,该对象无法参与循环。 如果对象 A 要求在构造对象 B 之前构造对象 B,并且对象 B 导入对象 A,则循环将无法解决,并且会发生合成错误。 在构造函数参数中声明的导入是必要的导入,必须全部满足后,才能使用需要这些导入的对象中的任何导出。
可选导入
该 Import
属性指定部件运作的要求。 如果无法完成导入,该部件的构成将失败,并且部件将不可用。
可以使用属性指定导入是AllowDefault
。 在这种情况下,即使导入与任何可用导出不匹配,合成也会成功,并且导入属性将设置为其属性类型的默认值(null
对于对象属性、 false
布尔值或数值属性的零)。以下类使用可选导入。
Public Class MyClass1
<Import(AllowDefault:=True)>
Public Property thePlugin As Plugin
'If no matching export is available,
'thePlugin will be set to null.
End Class
public class MyClass
{
[Import(AllowDefault = true)]
public Plugin thePlugin { get; set; }
//If no matching export is available,
//thePlugin will be set to null.
}
导入多个对象
只有当 Import
特性与一个导出匹配并且仅与一个导出匹配时,才能成功构成该特性。 其他类将生成组合错误。 若要导入与同一协定匹配的多个导出,请使用该 ImportMany
属性。 使用此属性标记的导入始终是可选的。 例如,如果没有匹配的导出,则组合不会失败。 以下类导入任意数量的IMyAddin
类型的导出。
Public Class MyClass1
<ImportMany()>
Public Property MyAddin As IEnumerable(Of IMyAddin)
End Class
public class MyClass
{
[ImportMany]
public IEnumerable<IMyAddin> MyAddin { get; set; }
}
可以使用普通 IEnumerable<T>
语法和方法访问导入的数组。 还可以改用普通数组(IMyAddin[]
)。
将此模式与 Lazy<T>
语法结合使用时非常重要。 例如,通过使用 ImportMany
、 IEnumerable<T>
和 Lazy<T>
,可以导入对任意数量的对象的间接引用,并仅实例化必要的对象。 下面的类演示此模式。
Public Class MyClass1
<ImportMany()>
Public Property MyAddin As IEnumerable(Of Lazy(Of IMyAddin))
End Class
public class MyClass
{
[ImportMany]
public IEnumerable<Lazy<IMyAddin>> MyAddin { get; set; }
}
避免发现
在某些情况下,你可能希望阻止某个部件被纳入目录。 例如,该部件可能是要从中继承但未使用的基类。 有两种方法可以完成此作。 首先,可以在部件类上使用 abstract
关键字。 抽象类从不提供导出,尽管它们可以向派生自它们的类提供继承的导出。
如果无法将类抽象化,则可以使用 PartNotDiscoverable
特性对其进行修饰。 使用此属性修饰的部件不会包含在任何目录中。 以下示例演示了这些模式。
DataOne
将被目录发现。 由于 DataTwo
是抽象的,因此不会发现它。 由于 DataThree
使用了该 PartNotDiscoverable
属性,因此不会发现它。
<Export()>
Public Class DataOne
'This part will be discovered
'as normal by the catalog.
End Class
<Export()>
Public MustInherit Class DataTwo
'This part will not be discovered
'by the catalog.
End Class
<PartNotDiscoverable()>
<Export()>
Public Class DataThree
'This part will also not be discovered
'by the catalog.
End Class
[Export]
public class DataOne
{
//This part will be discovered
//as normal by the catalog.
}
[Export]
public abstract class DataTwo
{
//This part will not be discovered
//by the catalog.
}
[PartNotDiscoverable]
[Export]
public class DataThree
{
//This part will also not be discovered
//by the catalog.
}
元数据和元数据视图
导出可提供有关自身的附加信息(称为“元数据” )。 元数据可用于将导出对象的属性传送到导入部件。 导入部件可以使用此数据来决定要使用的导出,或收集有关导出的信息,而无需构造导出。 因此,导入必须为延迟导入才能使用元数据。
若要使用元数据,通常声明称为 元数据视图的接口,该视图声明了哪些元数据可用。 元数据视图接口必须仅有属性,并且这些属性必须具有 get
访问器。 以下接口是一个示例元数据视图。
Public Interface IPluginMetadata
ReadOnly Property Name As String
<DefaultValue(1)>
ReadOnly Property Version As Integer
End Interface
public interface IPluginMetadata
{
string Name { get; }
[DefaultValue(1)]
int Version { get; }
}
还可以使用泛型集合 IDictionary<string, object>
作为元数据视图,但这会放弃类型检查的好处,应避免这样做。
通常,元数据视图中命名的所有属性都是必需的,不提供这些属性的任何导出都不会被视为匹配项。 该 DefaultValue
属性指定属性是可选的。 如果未包含该属性,则会为其分配指定为参数的 DefaultValue
默认值。 下面是用元数据修饰的两个不同的类。 这两个类都与以前的元数据视图匹配。
<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class MyLogger
Implements IPlugin
End Class
'Version is not required because of the DefaultValue
<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Disk Writer")>
Public Class DWriter
Implements IPlugin
End Class
[Export(typeof(IPlugin)),
ExportMetadata("Name", "Logger"),
ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
}
[Export(typeof(IPlugin)),
ExportMetadata("Name", "Disk Writer")]
//Version is not required because of the DefaultValue
public class DWriter : IPlugin
{
}
在属性Export
之后使用ExportMetadata
特性表示元数据。 每个元数据部分都由名称/值对组成。 元数据的名称部分必须与元数据视图中相应属性的名称匹配,并将该值分配给该属性。
它是指定将使用哪些元数据视图(如果有)的导入程序。 带有元数据的导入声明为惰性导入,元数据接口作为Lazy<T,T>
的第二个类型参数。 以下类使用元数据导入上一部分。
Public Class Addin
<Import()>
Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
[Import]
public Lazy<IPlugin, IPluginMetadata> plugin;
}
在许多情况下,需要将元数据与 ImportMany
属性合并,以便分析可用导入并选择并仅实例化一个,或筛选集合以匹配特定条件。 以下类仅实例化那些具有 IPlugin
值为“Logger”的 Name
对象。
Public Class User
<ImportMany()>
Public Property plugins As IEnumerable(Of Lazy(Of IPlugin, IPluginMetadata))
Public Function InstantiateLogger() As IPlugin
Dim logger As IPlugin
logger = Nothing
For Each Plugin As Lazy(Of IPlugin, IPluginMetadata) In plugins
If Plugin.Metadata.Name = "Logger" Then
logger = Plugin.Value
End If
Next
Return logger
End Function
End Class
public class User
{
[ImportMany]
public IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins;
public IPlugin InstantiateLogger()
{
IPlugin logger = null;
foreach (Lazy<IPlugin, IPluginMetadata> plugin in plugins)
{
if (plugin.Metadata.Name == "Logger")
logger = plugin.Value;
}
return logger;
}
}
导入和导出继承
如果一个类继承自某个组件,该类也可能成为组件。 导入始终由子类继承。 因此,部件的子类始终是一个部件,并拥有与其父类相同的导入。
使用 Export
特性声明的导出不由子类继承。 但是,部件可以使用特性 InheritedExport
导出自身。 部件的子类将继承并提供相同的导出,包括协定名称和协定类型。
Export
与特性不同,InheritedExport
只能在类级别应用,而不能应用于成员级别。 因此,成员级别的导出永远无法被继承。
以下四个类演示了导入和导出继承的原则。
NumTwo
继承自 NumOne
,因此 NumTwo
将导入 IMyData
。 普通出口不继承,因此 NumTwo
不会导出任何内容。
NumFour
继承自 NumThree
. 由于 NumThree
已使用 InheritedExport
,NumFour
因此有一个协定类型为 NumThree
的导出。 成员级导出绝不会继承,因此 IMyData
无法导出。
<Export()>
Public Class NumOne
<Import()>
Public Property MyData As IMyData
End Class
Public Class NumTwo
Inherits NumOne
'Imports are always inherited, so NumTwo will
'Import IMyData
'Ordinary exports are not inherited, so
'NumTwo will NOT export anything. As a result it
'will not be discovered by the catalog!
End Class
<InheritedExport()>
Public Class NumThree
<Export()>
Public Property MyData As IMyData
'This part provides two exports, one of
'contract type NumThree, and one of
'contract type IMyData.
End Class
Public Class NumFour
Inherits NumThree
'Because NumThree used InheritedExport,
'this part has one export with contract
'type NumThree.
'Member-level exports are never inherited,
'so IMyData is not exported.
End Class
[Export]
public class NumOne
{
[Import]
public IMyData MyData { get; set; }
}
public class NumTwo : NumOne
{
//Imports are always inherited, so NumTwo will
//import IMyData.
//Ordinary exports are not inherited, so
//NumTwo will NOT export anything. As a result it
//will not be discovered by the catalog!
}
[InheritedExport]
public class NumThree
{
[Export]
Public IMyData MyData { get; set; }
//This part provides two exports, one of
//contract type NumThree, and one of
//contract type IMyData.
}
public class NumFour : NumThree
{
//Because NumThree used InheritedExport,
//this part has one export with contract
//type NumThree.
//Member-level exports are never inherited,
//so IMyData is not exported.
}
如果存在与 InheritedExport
属性关联的元数据,该元数据也将继承。 (有关详细信息,请参阅前面的“元数据和元数据视图”部分。子类无法修改继承的元数据。 但是,通过重新声明 InheritedExport
具有相同协定名称和协定类型的属性,但使用新元数据,子类可以将继承的元数据替换为新元数据。 下面的类演示此原则。 部件 MegaLogger
继承自 Logger
并包括 InheritedExport
属性。 由于 MegaLogger
重新声明名为 Status 的新元数据,因此它不会从 Logger
中继承 Name 和 Version 元数据。
<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class Logger
Implements IPlugin
'Exports with contract type IPlugin
'and metadata "Name" and "Version".
End Class
Public Class SuperLogger
Inherits Logger
'Exports with contract type IPlugin and
'metadata "Name" and "Version", exactly the same
'as the Logger class.
End Class
<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Status", "Green")>
Public Class MegaLogger
Inherits Logger
'Exports with contract type IPlugin and
'metadata "Status" only. Re-declaring
'the attribute replaces all metadata.
End Class
[InheritedExport(typeof(IPlugin)),
ExportMetadata("Name", "Logger"),
ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
//Exports with contract type IPlugin and
//metadata "Name" and "Version".
}
public class SuperLogger : Logger
{
//Exports with contract type IPlugin and
//metadata "Name" and "Version", exactly the same
//as the Logger class.
}
[InheritedExport(typeof(IPlugin)),
ExportMetadata("Status", "Green")]
public class MegaLogger : Logger {
//Exports with contract type IPlugin and
//metadata "Status" only. Re-declaring
//the attribute replaces all metadata.
}
重新声明属性 InheritedExport
以替代元数据时,请确保协定类型相同。 (在前面的示例中,IPlugin
为协定类型。)如果协定类型不同,则第二个特性将创建另一个独立于部件的导出(而不是重写)。 通常,这意味着你必须在重写 InheritedExport
特性时显式指定协定类型,如前面的示例所示。
由于无法直接实例化接口,因此通常不能使用 Export
或 Import
属性修饰接口。 不过,可以在接口级别用 InheritedExport
特性修饰接口,并且任何实现类将随任何关联的元数据一起继承该导出。 但是,接口本身将不能作为一部分使用。
自定义导出属性
基本导出属性Export
和InheritedExport
可以扩展为包含元数据作为属性。 此方法可用于将类似的元数据应用于许多部分,或创建元数据属性的继承树。
自定义属性可以指定协定类型、协定名称或任何其他元数据。 为了定义自定义属性,继承自 ExportAttribute
(或 InheritedExportAttribute
)的类必须用 MetadataAttribute
属性进行修饰。 以下类定义自定义属性。
<MetadataAttribute()>
<AttributeUsage(AttributeTargets.Class, AllowMultiple:=false)>
Public Class MyAttribute
Inherits ExportAttribute
Public Property MyMetadata As String
Public Sub New(ByVal myMetadata As String)
MyBase.New(GetType(IMyAddin))
myMetadata = myMetadata
End Sub
End Class
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MyAttribute : ExportAttribute
{
public MyAttribute(string myMetadata)
: base(typeof(IMyAddin))
{
MyMetadata = myMetadata;
}
public string MyMetadata { get; private set; }
}
此类定义了一个名为MyAttribute
的自定义属性,具有合同类型IMyAddin
和一些名为MyMetadata
的元数据。 使用 MetadataAttribute
特性标记的类中的所有属性都被视为自定义属性中定义的元数据。 以下两个声明是等效的。
<Export(GetType(IMyAddin))>
<ExportMetadata("MyMetadata", "theData")>
Public Property myAddin As MyAddin
<MyAttribute("theData")>
Public Property myAddin As MyAddin
[Export(typeof(IMyAddin)),
ExportMetadata("MyMetadata", "theData")]
public MyAddin myAddin { get; set; }
[MyAttribute("theData")]
public MyAddin myAddin { get; set; }
在第一个声明中,显式定义了协定类型和元数据。 在第二个声明中,协定类型和元数据在自定义属性中是隐式的。 特别是在必须将大量相同的元数据应用于许多部分(例如作者或版权信息)的情况下,使用自定义属性可以节省大量时间和重复。 此外,可以创建自定义属性的继承树以允许变体。
若要在自定义属性中创建可选元数据,可以使用该 DefaultValue
属性。 当此属性应用于自定义属性类中的属性时,它指定修饰的属性是可选的,并且不必由导出程序提供。 如果未提供该属性的值,则会为其属性类型(通常 null
为 false
或 0)分配默认值。
创建策略
当组件指定导入并执行组合操作时,组合容器将尝试查找匹配的导出。 如果导入成功与导出匹配,则导入成员将设置为导出对象的实例。 此实例的来源由导出部件的 创建策略控制。
这两个可能的创建策略 是共享 的, 也是非共享的。 在具有该协定的部件的容器中,创建策略为“共享”的部件将在每个导入之间共享。 当合成引擎找到匹配项并且必须设置导入属性时,仅当部件尚不存在时,它才会实例化部件的新副本;否则,它将提供现有副本。 这意味着许多对象可能引用同一部分。 此类部件不应依赖于可能会从许多地方更改的内部状态。 此策略适用于静态部件、提供服务的部件以及消耗大量内存或其他资源的部件。
在每次找到部件的其中一个导出的匹配导入时,将会创建创建策略为“非共享”的部件。 因此,将为与部件的其中一个已导出协定匹配的容器中的每个导入实例化一个新副本。 这些副本的内部状态不会被共享。 此策略适用于每个导入需要其自己的内部状态的部件。
导入和导出都可以指定部件的创建策略,选择的值包括Shared
、NonShared
或Any
。 默认值是 Any
适用于导入和导出。 指定 Shared
或 NonShared
的导出将仅匹配指定相同或指定 Any
的导入。 同样,指定 Shared
或 NonShared
的导入将仅匹配指定相同或指定 Any
的导出。 导入和导出的创建策略不兼容时不被视为匹配项,就像其合同名称或合同类型不匹配时一样。 如果导入和导出都指定了Any
,或者没有指定创建策略而默认使用Any
,那么创建策略将默认为共享。
以下示例说明了导入和导出均指定创建策略。
PartOne
不指定创建策略,因此默认值为 Any
。
PartTwo
不指定创建策略,因此默认值为 Any
。 由于导入和导出都默认使用Any
,因此PartOne
将被共享。
PartThree
指定创建 Shared
策略,因此 PartTwo
和 PartThree
将共享 PartOne
的相同副本。
PartFour
指定NonShared
的创建策略,因此在PartFour
中,PartFive
将不会共享。
PartSix
指定 NonShared
创建策略。
PartFive
和 PartSix
将分别获得 PartFour
的独立副本。
PartSeven
指定 Shared
创建策略。 由于没有创建策略为 PartFour
的已导出 Shared
,因此 PartSeven
导入不与任何内容匹配,并且将不会得到满足。
<Export()>
Public Class PartOne
'The default creation policy for an export is Any.
End Class
Public Class PartTwo
<Import()>
Public Property partOne As PartOne
'The default creation policy for an import is Any.
'If both policies are Any, the part will be shared.
End Class
Public Class PartThree
<Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
Public Property partOne As PartOne
'The Shared creation policy is explicitly specified.
'PartTwo and PartThree will receive references to the
'SAME copy of PartOne.
End Class
<Export()>
<PartCreationPolicy(CreationPolicy.NonShared)>
Public Class PartFour
'The NonShared creation policy is explicitly specified.
End Class
Public Class PartFive
<Import()>
Public Property partFour As PartFour
'The default creation policy for an import is Any.
'Since the export's creation policy was explicitly
'defined, the creation policy for this property will
'be non-shared.
End Class
Public Class PartSix
<Import(RequiredCreationPolicy:=CreationPolicy.NonShared)>
Public Property partFour As PartFour
'Both import and export specify matching creation
'policies. PartFive and PartSix will each receive
'SEPARATE copies of PartFour, each with its own
'internal state.
End Class
Public Class PartSeven
<Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
Public Property partFour As PartFour
'A creation policy mismatch. Because there is no
'exported PartFour with a creation policy of Shared,
'this import does not match anything and will not be
'filled.
End Class
[Export]
public class PartOne
{
//The default creation policy for an export is Any.
}
public class PartTwo
{
[Import]
public PartOne partOne { get; set; }
//The default creation policy for an import is Any.
//If both policies are Any, the part will be shared.
}
public class PartThree
{
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
public PartOne partOne { get; set; }
//The Shared creation policy is explicitly specified.
//PartTwo and PartThree will receive references to the
//SAME copy of PartOne.
}
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class PartFour
{
//The NonShared creation policy is explicitly specified.
}
public class PartFive
{
[Import]
public PartFour partFour { get; set; }
//The default creation policy for an import is Any.
//Since the export's creation policy was explicitly
//defined, the creation policy for this property will
//be non-shared.
}
public class PartSix
{
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public PartFour partFour { get; set; }
//Both import and export specify matching creation
//policies. PartFive and PartSix will each receive
//SEPARATE copies of PartFour, each with its own
//internal state.
}
public class PartSeven
{
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
public PartFour partFour { get; set; }
//A creation policy mismatch. Because there is no
//exported PartFour with a creation policy of Shared,
//this import does not match anything and will not be
//filled.
}
生命周期和处置
由于部件托管在合成容器中,因此其生命周期可能比普通对象更复杂。 部件可以实现两个重要的生命周期相关接口: IDisposable
和 IPartImportsSatisfiedNotification
。
需要在关闭时执行工作的部件或需要释放资源的部件,应像往常在 .NET Framework 对象中那样实现 IDisposable
。 但是,由于容器创建和维护对部件的引用,因此只有拥有部件的容器应调用其上的 Dispose
方法。 容器本身实现 IDisposable
,并且作为 Dispose
中其清理的一部分,它将对拥有的所有部件调用 Dispose
。 因此,当不再需要组合容器及其拥有的任何部件时,你应始终释放该组合容器。
对于长期构成容器,具有非共享创建策略的部件的内存消耗可能会成为问题。 这些非共享部件可以多次创建,并且在容器本身被释放之前将不会得到释放。 为了解决此问题,容器提供 ReleaseExport
该方法。 如果对非共享导出调用此方法,将会从组合容器中移除该导出并将其释放。 仅由移除的导出使用的部件以及树中更深层的诸如此类部件将也会被移除并得到释放。 这样,就可以回收和重新利用资源,而无需处理合成容器本身。
IPartImportsSatisfiedNotification
包含一个名为 OnImportsSatisfied
的方法。 当组合已完成并且部件的导入可供使用时,组合窗口将对实现接口的任何部件调用此方法。 部件由合成引擎创建,以填充其他部件的导入。 在部件导入设置之前,不能执行依赖于部件构造函数中导入值或操作这些值的任何初始化,除非这些值已使用 ImportingConstructor
特性指定为先决条件。 这通常是首选方法,但在某些情况下,构造函数注入可能不可用。 在这些情况下,可以执行 OnImportsSatisfied
初始化,并且部件应实现 IPartImportsSatisfiedNotification
。