MEF核心笔记(4)细说MEF中的Attribute [下]

今天,我们继续MEF的学习记录,这次内容感觉比较重要,所以,特别放到单独一篇来说,这也是MEF很有特色的地方,相信这其中的亮点,会让你感触良多的。

一、部件的创建规则

我们知道,在目前主流的IoC框架里,注入对象的创建都可以进行个性化配置,例如是否以单例方式创建(也就是共享一个对象,给所有需要注入的地方调用),不仅如此,如果熟悉Remoting技术的朋友应该也会接触到服务对象的实例创建规则,另外WCF服务对象也亦是如此,他们都有各自的创建规则。同样,MEF的部件也有它的创建规则。

涉及到MEF部件的创建规则,首先我们要看下PartCreationPolicyAttribute这个特性,因为部件创建的规则,主要是靠该特性来控制的。该特性只有一个属性CreationPolicy,类型为System.ComponentModel.Composition.CreationPolicy的枚举:

CreationPolicy枚举

对应的ImportAttributeImportManyAttribute,都有一个RequiredCreationPolicy的属性,类型也是此枚举。从这里我们就不难看出,使用PartCreationPolicyAttribute我们可以指定导出部件的创建规则,通过导入的RequiredCreationPolicy的属性我们可以设定导入类型的创建规则。

根据CreationPolicy枚举的值,我们很容易就能看出其代表的意义,Shared代表共享部件,即单例,所有的导入都使用一个实例,如果组合引擎中没有该实例,则会创建,一旦有了,就不会再创建;NonSharedShared相对应,即每次导入都创建一个新的实例,所有导入的实例都拥有自己唯一的状态,数据不共享;Any只是为了匹配导入导出,有下面一张匹配表:

导出的CreationPolicy 导入的CreationPolicy
Any Any、NonShared、Shared
NoneShared NoneShared、Any
Shared Shared、Any

只有满足上面这张表,导入导出才会匹配,下面我们做一个很简单的示例:

1
namespace MEFTest {

    class Program {

        private static CompositionContainer _container;
        static void Main(string[] args) {

            var catalog = new AssemblyCatalog(typeof(Program).Assembly);
            _container = new CompositionContainer(catalog);

            var studentManager1 = _container.GetExportedValue<StudentManager>();
            var studentManager2 = _container.GetExportedValue<StudentManager>();

            Console.WriteLine(object.ReferenceEquals(studentManager1, studentManager2));
            Console.WriteLine(object.ReferenceEquals(studentManager1.Student, 
                studentManager2.Student));

            while (true) {
                Console.ReadLine();
            }

        }
    }

    //单例导出
    [Export, PartCreationPolicy(CreationPolicy.Shared)]
    public class Student {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    //非单例导出
    [Export, PartCreationPolicy(CreationPolicy.NonShared)]
    public class StudentManager {
        //默认的是 Any
        [Import]
        public Student Student { get; set; }
    }
}

最后输出的是一个falsetrue,看懂了这个示例,你就看懂整个创建规则了,如果没有看懂,抱歉,只能说明,我的示例写得太烂了。

此外,我们可以预先在容器中定义导出,这样的定义不需要Export特性描述类型,并且这样输出的永远是单例:

1
_container.ComposeExportedValue<DateTime>(DateTime.Now);
Console.WriteLine(_container.GetExportedValue<DateTime>());

二、元数据和元数据视图

在MEF中,我们可以在导出部件时附加一些数据,而这些附加导出的数据就是元数据,附加导出数据的结构就是元数据视图,这是我觉得MEF中,最令人激动的功能。

导出元数据,我们使用ExportMetadata特性,设置该特性的NameValue,即可导出对应的元数据,我们以示例来说:

1
class Program {

    private static CompositionContainer _container;
    static void Main(string[] args) {

        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        _container = new CompositionContainer(catalog);

        var studentManager = _container.GetExportedValue<StudentManager>();
        Console.WriteLine(studentManager.Student.Metadata.ClassName);

        while (true) {
            Console.ReadLine();
        }

    }
}

public interface IClassMetadata {
    string ClassName { get; }

    [DefaultValue("")]
    string OtherInfo { get; }
}

[Export]
[ExportMetadata("ClassName", "一年级三班")]
public class Student {
    public string Name { get; set; }
    public int Age { get; set; }
}

[Export]
public class StudentManager {

    [Import]
    public Lazy<Student, IClassMetadata> Student { get; set; }
}

在该示例中,IClassMetadata即是元数据视图,我们导出的元数据必须满足该接口格式,否则StudentManagerImprot就会失败。我们在Student类上只导出了ClassName,而我们的元数据视图中还有一个OtherInfo的属性,这里需要注意一下,如果要提供默认值,必须标记上DefaultValue,否则如果不赋值(导出)的话,就匹配不了该元数据视图,也就是说,如果我们将DefaultValue去掉,该程序就不会正确执行了(StudentManagerImprot会失败)。

除了使用ExportMetadata特性导出元数据外,我们还可定义自己的导出特性来导出元数据,我们可以继承ExportAttribute,并且一定要有MetadataAttribute特性,以上的示例,我们可以改成这样:

1
public interface IClassMetadata {
    string ClassName { get; }
    string OtherInfo { get; }
}

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ExportStudent : ExportAttribute, IClassMetadata {
    public ExportStudent()
        : base() { }
    public ExportStudent(string contractName)
        : base(contractName) { }
    public ExportStudent(Type contractType)
        : base(contractType) { }
    public ExportStudent(string contractName, Type contractType)
        : base(contractName, contractType) { }

    public string ClassName { get; set; }

    //如果可选,必须要加上
    [DefaultValue("")]
    public string OtherInfo { get; set; }

}

[ExportStudent(ClassName = "一年级三班")]
public class Student {
    public string Name { get; set; }
    public int Age { get; set; }
}

通过继承ExportAttribute,我们可以实现比较强类型的编程,再也不怕字符串拼错,不过相比与ExportMetadata似乎是麻烦了一点点。

三、部件组装通知

这是个比较简单,但又很有用的功能,特别是在我们需要完成一些自动化的操作时(例如日志)。部件组装通知,就是当某个组件引用的部件都能满足导入,在返回已经组装完成的组件之前,先通知该组件。

若要得到通知,我们只要实现IPartImportsSatisfiedNotification接口即可,该接口有一个OnImportsSatisfied的方法,即通知组件部件组装的地方。请看简单的示例:

1
class Program {

    private static CompositionContainer _container;
    static void Main(string[] args) {

        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        _container = new CompositionContainer(catalog);

        var studentManager = _container.GetExportedValue<MyComponent>();

        while (true) {
            Console.ReadLine();
        }

    }
}

[Export]
class MyComponent : IPartImportsSatisfiedNotification {
    public void OnImportsSatisfied() {
        Console.WriteLine("OK!~");
    }
}

该示例很简单,但也将IPartImportsSatisfiedNotification淋漓尽致的体现了,由于MyComponent满足了组装条件,所以该通知一定能得到执行。

四、总结

这一篇和前一篇是MEF非常核心的内容,了解到这里,我们基本上已经完全可以胜任MEF的开发使用了。后续打算开发一个程序,来深入体会MEF具体使用,以及设计层面上的思想,大家一起期待吧。