组合部件使用[System.ComponentModel.Composition.ImportAttribute]特性声明导入。与导出类似,也有几种成员支持,即为字段、属性和构造器参数。同样,我们也来看下该特性类的声明:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field
| AttributeTargets.Property, AllowMultiple=false, Inherited=false)] public class ImportAttribute : Attribute, IAttributedImport { }
果然,支持这三种成员(这里Parameter而不是GenericParameter),也不支持继承,另外,与[Export]不同的是,在同一个目标上不能应用多次该特性。
属性倒入
为了向属性导入一个值,用[Import]来声明该属性。这是我们在前面的例子中使用的。例如下面倒入一个IMessageSender的代码片段:
[Import] public IMessageSender MessageSender { get; set; }
构造函数参数
你也可以指定构造函数参数导入。这意味着你是将构造函数参数添加各自的导入,而不是将属性添加各自的导入。为了使用它,按照下面的步骤:
- 添加一个[System.ComponentModel.Composition.ImportingConstructorAttribute]特性到应该被MEF使用的构造函数上。
- 为构造函数参数添加各自的导入
例如,下面的代码在Program类构造函数上导入一个message sender。
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace ImportsDeclaring { class Program { static void Main(string[] args) { Program p = new Program(); p.Run(); Console.ReadKey(); } void Run() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); container.ComposeExportedValue<IMessageSender>(new EmailSender()); var s = container.GetExportedValue<IService>(); s.Work(); } } [Export(typeof(IService))] class SenderService:IService { private IMessageSender sender; [ImportingConstructor] public SenderService([Import(AllowDefault = true)]IMessageSender sender) { this.sender = sender; } void IService.Work() { if (sender != null) sender.Send("Hi,MEF"); } } interface IService { void Work(); } interface IMessageSender { void Send(string msg); } class EmailSender : IMessageSender { public void Send(string msg) { Console.WriteLine("Email Sent:" + msg); } } }
效果如图:
参数导入
在构造函数上定义导出有这么几种不同的方式:
- 隐式导入-容器将会默认地使用参数的类型来识别契约。
- 显示导入-给参数添加[Import]特性,以此来制定要导入的契约
例如下面的两种方式等价:
[ImportingConstructor]
public SenderService(IMessageSender sender) { this.sender = sender; } [ImportingConstructor] public SenderService([Import(typeof(IMessageSender))]IMessageSender sender) { this.sender = sender; }
字段导入
MEF也支持直接将值导入到字段。形式上和属性导入很像。但是需要注意的是,私有成员(字段、属性和方法)的导入或导出仅仅支持在中/部分信任应用中,而在完全信任应用的支持上可能会有问题。
可选导入
MEF允许你指定一个可选的导入。当你启用它时,如果有可用的导出,这个容器将会提供该导出,并且设置导入为Default(T)。为了利用导入选项,在[Import]上设置AllowDefault=true。在上面我们已经使用了它,作用是当我们未执行container.ComposeExportedValue<IMessageSender>(new EmailSender()),即未给构造函数参数导入值,这时,[Import(AllowDefault=true)]相当于container.ComposeExportedValue<IMessageSender>(null),虽然参数值为null,但是契约匹配仍然生效,而如果AllowDefault=false(默认值),这时,构造器参数导入无法匹配导出,在执行下一行代码var s = container.GetExportedValue<IService>();就会报错。
导出集合
除了单个导出之外,你可以使用[ImportMany]来导出集合。这意味着指定的契约的所有实例都将被导入到这个容器。
MEF部件也支持重组。这意味着当在容器中新的导出可用时,集合会自动更新。
IPartImportsSatisfiedNotification接口
在某些场合,当MEF在处理类的实例导入过程时,该类能得到通知,对于该类来说这可能是重要的。如果是这种情况的话,请实现IPartImportsSatisfiedNotification接口,这个接口只有一个方法:OnImportsSatisfied,当所有满足部件的导入都满足时该方法将被调用。
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ImportsDeclaring { class Example { static void Main(string[] args) { Example e = new Example(); Notifier noter = new Notifier(); var container = new CompositionContainer(); container.ComposeParts(noter, new EmailSender(), new TcpSender(), new PhoneSender()); noter.Notify("Hi,MEF"); Console.WriteLine("-----------------"); noter.MessageSender.Send("Hi,MEF"); Console.ReadKey(); } } class Notifier:IPartImportsSatisfiedNotification { [ImportMany] public IEnumerable<IMessageSender> Senders { get; set; } [Import("PhoneSender")] public IMessageSender MessageSender { get; set; } public void Notify(string msg) { foreach (var item in Senders) { item.Send(msg); } } public void OnImportsSatisfied() { Console.WriteLine("all imports that could be satisfied have been satisfied"); } } [Export(typeof(IMessageSender))] class EmailSender : IMessageSender { public void Send(string msg) { Console.WriteLine("Email Sent:" + msg); } } [Export(typeof(IMessageSender))] class TcpSender : IMessageSender { public void Send(string msg) { Console.WriteLine("TCP Sent:" + msg); } } [Export("PhoneSender", typeof(IMessageSender))] class PhoneSender : IMessageSender { public void Send(string msg) { Console.WriteLine("Phone Sent:" + msg); } } }
效果如下: