MEF學習系列(5): 建立策略(Creation Policy)和生命週期(Life Cycle)
前言
在前面的介紹中我們已經知道:匯入和匯出的匹配需要ContractType,ContractName,Metadata都匹配,這裡我們還要介紹一個新東西:建立策略(Creation Policy)。有時候我們在容器中的例項在每一個匯入之間共享,即單例;有時候我們需要讓每一個匯入都擁有一個各自的例項,這在MEF中就是由建立策略決定的。
宣告週期(Life Cycle),每一個MEF的部件在容器中都有自己的生命週期,何時建立,何時釋放登。本文主要介紹一下MEF中建立策略和生命週期。
建立策略
建立策略,其實就是組合容器決定如何建立部件。在組合容器組合部件時,如果匯入和匯出匹配成功,則組合容器會將匯入成員的值設定成為匯出的例項
MEF的建立策略有:Shared(共享)和NonShared(非共享)。
使用Shared建立策略的部件將在每一個匯入部件中共享例項。僅當容器中沒有該部件的例項時才會建立新例項。使用共享策略建立的部件可以是提供服務的部件,以及較佔用記憶體的部件。他們的內部狀態應該儘可能少得受外界影響。
使用NonShared建立策略的部件在匹配每一個匯入時都會有新的例項。這些部件的內部狀態都是相互獨立的,當某些部件需要保持特定的狀態時可以使用這種策略。
MEF關於建立策略提供的特性有3種:Shared、NonShared、Any(請注意Any和Shared的區別,建立策略只有兩種)。匯入和匯出的預設值都是Any。當匯入匯出都是用預設建立策略,或者都是預設,MEF將預設建立策略為Shard。建立策略的匹配與否也決定了匯出部件和匯入能否成功匹配。以下情況可視為匹配成功:
1. 策略標記為Any的匯出部件可以與任何匯入部件策略(Shared,NonShared,Any,預設)匹配成功。
2. 策略標記為Shared的匯出部件可以與標記為Shared、Any和預設(也就是什麼都不寫只有[Import])的匯入部件匹配成功。唯獨不能與NonShared匯入部件匹配成功。
3. 策略標記為NonShared的匯出部件可以與標記為NonShared、Any和預設(也就是什麼都不寫只有[Import])的匯入部件匹配成功。唯獨不能與Shared匯入部件匹配成功。
DEMO原始碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.Composition; namespace MEFDemo5 { //定義匯出部件,建立策略為非共享。注意這裡並沒有實現其他介面 [Export] [PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)] //建立匯出策略為非共享策略 public class FileLog { public void WriteLog() { Console.WriteLine("This is FileLog"); } } }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace MEFDemo5
{
//定義匯出部件,建立策略為非共享
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Shared)]
public class DBLog
{
public void WriteLog()
{
Console.WriteLine("This is DBLog");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace MEFDemo5
{
//定義匯出部件,匯出策略為Any
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Any)]
public class AnyLog
{
public void WriteLog()
{
Console.WriteLine("This is AnyLog");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
namespace MEFDemo5
{
class Program
{
//匯入成功,因為FileLog類的匯出為NonShared策略,它可以與匯入策略是Nonshared的匯入匹配成功
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public FileLog MyFileLog1;
//匯入成功,因為FileLog類的匯出為NonShared策略,它可以與匯入策略是Any的匹配成功
[Import(RequiredCreationPolicy = CreationPolicy.Any)]
public FileLog MyFileLog2;
//匹配失敗,因為FileLog類的匯出為NonShared策略,它不可以與匯入策略是Shared的策略匹配成功
//[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
//public FileLog MyFileLog3;
//匹配成功,因為FileLog類的匯出為Nonshared策略,它可以與匯入策略什麼都不寫的(預設是Any)策略匹配成功
[Import]
public FileLog MyFileLog4;
//匹配成功,因為DBLog類的匯出是Shared策略,它可以與匯入策略什麼都不寫(預設是Any)的策略匹配成功
[Import]
public DBLog MyDBLog1;
//匹配成功,因為DBLog類的匯出策略是Shared,它可以與匯入策略是Shared的策略匹配成功
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
public DBLog MyDBLog2;
//匹配失敗,因為DBLog類的匯出策略是Shared,他無法與匯入策略是Shared的策略匹配成功
//[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
//public DBLog MyDBLog3;
//匹配成功,因為DBLog類的匯出策略是Shared,他可以與匯入策略是Any的策略匹配成功
[Import(RequiredCreationPolicy = CreationPolicy.Any)]
public DBLog MyDBLog4;
//匹配成功,因為AnyLog類的匯出策略是Any,則它可以與匯入策略是Any的屬性匹配成功
[Import(RequiredCreationPolicy = CreationPolicy.Any)]
public AnyLog AnyLog1;
//匹配成功,因為AnyLog類的匯出策略是Any,則它可以與匯入策略是NonShared的屬性匹配成功
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public AnyLog AnyLog2;
//匹配成功,匯出策略是Any的,匯入策略是任何情況都可以匯入成功
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
public AnyLog AnyLog3;
//匹配成功,匯出策略是Any的,匯入策略是任何情況都可以匯入成功
[Import]
public AnyLog AnyLog4;
static void Main(string[] args)
{
Program pro = new Program();
pro.Compose();
pro.MyFileLog1.WriteLog();
pro.MyFileLog2.WriteLog();
//pro.MyFileLog3.WriteLog();//匹配失敗 匯出策略是NonShared,無法與匯入策略是Shared的匹配成功。
pro.MyFileLog4.WriteLog();
pro.MyDBLog1.WriteLog();
pro.MyDBLog2.WriteLog();
//pro.MyDBLog3.WriteLog();//匹配失敗 匯出策略是Shared,無法與匯入策略是NonShared的匹配成功。
pro.MyDBLog4.WriteLog();
pro.AnyLog1.WriteLog();
pro.AnyLog2.WriteLog();
pro.AnyLog3.WriteLog();
pro.AnyLog4.WriteLog();
Console.Read();
}
private void Compose()
{
//建立目錄
var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
//建立容器
var container = new CompositionContainer(catalog);
//進行組合
container.ComposeParts(this);
}
}
}
注意:到目前為止我們知道可以影響匯出部件成功匹配的主要因素大概有:ContractType(協定型別),ContractName(協定名稱),Metadata(元資料),CreationPolicy(建立策略)。
生命週期
在MEF中,宣告週期是比較複雜的,在MSDN中也只是用一句話帶過:
由於部件承載與組合容器中,因此其生命週期可能比普通物件更復雜。部件可實現兩個重要的生命週期相關介面:IDisposable和IPartImportsSatisfiedNotification。
容器本身實現了IDisposable,在容器Dispose方法被呼叫時才會釋放資源。一般情況下,部件只有在容器釋放時才會釋放資源。聯想到上面介紹的建立策略,有人不禁要問如果建立策略為非共享的部件過多,很佔用很多資源,不過不用擔心:容器提供了ReleaseExpor方法。此方法可以釋放匯出部件,並對部件佔用的資源進行釋放。
如果部件實現了介面IPartImportsSatisfiedNotification,當組合已完成並且部件的匯入可供使用時,組合視窗將對部件呼叫介面中的方法OnImportsSatisfied。
IPartImportsSatisfiedNotification包含一個名為OnImportsSatisfied的方法。當組合已完成並且部件的匯入可供使用時,組合視窗將對實現介面的任何部件呼叫此方法。部件是組合引擎建立,用於滿足其他部件的匯入。在設定好部件的匯入之前,您無法執行任何依賴於部件建構函式中的匯入值或者對這些值進行操作的初始化,除非已通過使用ImportingConstruction特性將這些指定為必備。此方法通常為首選方法,但在某些情況下,建構函式注入可能不可用。在這些情況下,可以在OnImportsSatisfied中執行初始化,並且部件應實現IPartImportsSatisfiedNotification。
靈活運用部件的生命週期在具體使用MEF中會有很大的幫助,如:我們可能將WCF,EF做成元件,然後使用MEF整合這些元件,那麼在組合成功後,我們可能需要讀取WCF配置釋出服務,或者讀取DB.config建立資料庫連線等。這時在OnImportsSatisfied中完成這些操作可能效果更好。