MEF核心筆記(4)細說MEF中的Attribute [下]


今天,我們繼續MEF的學習記錄,這次內容感覺比較重要,所以,特別放到單獨一篇來說,這也是MEF很有特色的地方,相信這其中的亮點,會讓你感觸良多的。

本篇主要有以下幾節內容:

一、部件的創建規則

我們知道,在目前主流的IoC框架里,注入對象的創建都可以進行個性化配置,例如是否以單例方式創建(也就是共享一個對象,給所有需要注入的地方調用),不僅如此,如果熟悉【Remoting】技術的朋友應該也會接觸到服務對象的實例創建規則,另外WCF服務對象也亦是如此,他們都有各自的創建規則。同樣,MEF的部件也有它的創建規則。

涉及到MEF部件的創建規則,首先我們要看下【PartCreationPolicyAttribute】這個特性,因為部件創建的規則,主要是靠該特性來控制的。該特性只有一個屬性【CreationPolicy】,類型為【System.ComponentModel.Composition.CreationPolicy】的枚舉:

image

對應的【ImportAttribute】、【ImportManyAttribute】,都有一個【RequiredCreationPolicy】的屬性,類型也是此枚舉。從這里我們就不難看出,使用【PartCreationPolicyAttribute】我們可以指定導出部件的創建規則,通過導入的【RequiredCreationPolicy】的屬性我們可以設定導入類型的創建規則。

根據【CreationPolicy】枚舉的值,我們很容易就能看出其代表的意義,【Shared】代表共享部件,即單例,所有的導入都使用一個實例,如果組合引擎中沒有該實例,則會創建,一旦有了,就不會再創建;【NonShared】和【Shared】相對應,即每次導入都創建一個新的實例,所有導入的實例都擁有自己唯一的狀態,數據不共享;【Any】只是為了匹配導入導出,有下面一張匹配表:

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

只有滿足上面這張表,導入導出才會匹配,下面我們做一個很簡單的示例:

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; }
    }
}

最后輸出的是一個false和true,看懂了這個示例,你就看懂整個創建規則了,如果沒有看懂,抱歉,只能說明,我的示例寫得太爛了。

此外,我們可以預先在容器中定義導出,這樣的定義不需要【Export】特性描述類型,並且這樣輸出的永遠是單例:

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

二、元數據和元數據視圖

在MEF中,我們可以在導出部件時附加一些數據,而這些附加導出的數據就是元數據,附加導出數據的結構就是元數據視圖,這是我覺得MEF中,最令人激動的功能。

導出元數據,我們使用【ExportMetadata】特性,設置該特性的【Name】和【Value】,即可導出對應的元數據,我們以示例來說:

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】即是元數據視圖,我們導出的元數據必須滿足該接口格式,否則【StudentManager】的【Improt】就會失敗。我們在【Student】類上只導出了【ClassName】,而我們的元數據視圖中還有一個【OtherInfo】的屬性,這里需要注意一下,如果要提供默認值,必須標記上【DefaultValue】,否則如果不賦值(導出)的話,就匹配不了該元數據視圖,也就是說,如果我們將【DefaultValue】去掉,該程序就不會正確執行了(【StudentManager】的【Improt】會失敗)。

除了使用【ExportMetadata】特性導出元數據外,我們還可定義自己的導出特性來導出元數據,我們可以繼承【ExportAttribute】,並且一定要有【MetadataAttribute】特性,以上的示例,我們可以改成這樣:

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】的方法,即通知組件部件組裝的地方。請看簡單的示例:

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具體使用,以及設計層面上的思想,大家一起期待吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM