C#在MEF框架中實(shí)現(xiàn)延遲加載部件
在MEF的宿主中,當(dāng)我們通過Import聲明導(dǎo)入的對象時(shí),組裝(Compose)的時(shí)候會創(chuàng)建該對象。例如:
interface ILogger { void Log(string message); } [Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine("logger 1" + message); } } class Host { [Import] ILogger _logger = null; public Host() { var catalog = new AssemblyCatalog(this.GetType().Assembly); var container = new CompositionContainer(catalog); //這兒會創(chuàng)建ConsoleLogger對象 container.ComposeParts(this); _logger.Log("hello world"); } }
有的時(shí)候,有些組件的創(chuàng)建開銷比較大,但又不會立即使用。此時(shí),我們希望通過延遲初始化的方式將其延遲到使用的時(shí)候創(chuàng)建,從而提高性能(常見的是提高啟動速度)。MEF是支持這一模式的,我們只需要修改一下導(dǎo)入的聲明形式即可。
[Import] Lazy<ILogger> _logger = null;
這樣,Logger就會延遲到第一次使用的時(shí)候創(chuàng)建了。
元數(shù)據(jù)MetaData
有的時(shí)候,對于同一個(gè)服務(wù)有多個(gè)提供者,我們需要從中選擇一個(gè)使用。MEF提供了ImportMany來解決這一需求。
有的時(shí)候,對于同一個(gè)服務(wù)有多個(gè)提供者,我們需要從中選擇一個(gè)使用。MEF提供了ImportMany來解決這一需求。
[Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } } [Export(typeof(ILogger))] class DbLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } } class Host { [ImportMany] ILogger[] _logger = null; public Host() { var catalog = new AssemblyCatalog(this.GetType().Assembly); var container = new CompositionContainer(catalog); container.ComposeParts(this); _logger.FirstOrDefault(i => i is DbLogger).Log("hello world"); } }
此時(shí),如果我們想使用延遲導(dǎo)入的時(shí)候,就會變成如下形式:
class Host { [ImportMany] Lazy<ILogger>[] _loggerServices = null; public Host() { var catalog = new AssemblyCatalog(this.GetType().Assembly); var container = new CompositionContainer(catalog); //這兒會創(chuàng)建ConsoleLogger對象 container.ComposeParts(this); _loggerServices.FirstOrDefault(i => i.Value is DbLogger).Value.Log("hello world"); } }
咋一看并沒有什么問題,所有的Logger都是延遲創(chuàng)建的。但是仔細(xì)分析一下就會發(fā)現(xiàn),要找到DbLogger的時(shí)候,必須遍歷所有的_loggerServices,這個(gè)遍歷會導(dǎo)致創(chuàng)建Logger。也就是說,使用第一個(gè)Logger的時(shí)候可能創(chuàng)建所有的Logger。
那么,如何實(shí)現(xiàn)我們只創(chuàng)建所需要的Logger呢? 這個(gè)時(shí)候就輪到元數(shù)據(jù)出場了,MEF中的元數(shù)據(jù)可以將一個(gè)數(shù)據(jù)附加到Export的服務(wù)對象中一并導(dǎo)出,從而可以通過元素?fù)?jù)找到對應(yīng)的服務(wù)。首先我們看看最終的效果吧:
public interface ILoggerData { string Name { get; } } class Host { [ImportMany] Lazy<ILogger, ILoggerData>[] _logger = null; public Host() { var catalog = new AssemblyCatalog(this.GetType().Assembly); var container = new CompositionContainer(catalog); container.ComposeParts(this); _logger.FirstOrDefault(i => i.Metadata.Name == "DB Logger").Value.Log("hello world"); } }
這里首先聲明了一個(gè)元數(shù)據(jù)類型的接口ILoggerData,然后,導(dǎo)入的對象變成了Lazy<ILogger, ILoggerData>,這個(gè)對象有一個(gè)屬性為Metadata,它的類型就是剛才聲明的ILoggerData。導(dǎo)出的ILogger對象是延遲創(chuàng)建的,而元數(shù)據(jù)不是延遲創(chuàng)建的。我們可以通過遍歷ILoggerData來找到所需要的Logger對象,從而實(shí)現(xiàn)只創(chuàng)建所使用的Logger對象。
現(xiàn)在的問題是:如何在導(dǎo)出的時(shí)候聲明相關(guān)的元數(shù)據(jù)。MEF提供了兩種方式:
通過ExportMetadataAttribute標(biāo)記聲明
這種方式是在導(dǎo)出的服務(wù)的時(shí)候一并通過ExportMetaDataAttribute屬性標(biāo)記元素?fù)?jù):
[ExportMetadata("Name", "Console Logger")] [Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } } [ExportMetadata("Name", "DB Logger")] [Export(typeof(ILogger))] class DbLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } }
ExportMetaDataAttribute有兩個(gè)參數(shù),Name和Value,Name為屬性名稱,Value為屬性值。Compse的時(shí)候,MEF會先創(chuàng)建一個(gè)實(shí)現(xiàn)了元數(shù)據(jù)對象,然后將根據(jù)將Value值賦值給Name所對應(yīng)的屬性名稱。
這么做雖然比較簡單,但是它有兩個(gè)弊端:
- 1. 屬性名稱不是強(qiáng)類型
這里我們必須字符串來給標(biāo)志屬性名稱,它們之間并沒有語法級的一致性檢查,在缺少nameof運(yùn)算符的現(xiàn)在,一旦元數(shù)據(jù)屬性名稱更改的話是非常容易出錯(cuò)的。
- 2. 如果元數(shù)據(jù)有多個(gè)值的話賦值顯得非常累贅。
假如我們增加了一個(gè)Priority類型的屬性,
public interface ILoggerData { string Name { get; } int Priority { get; } }
此時(shí),必須對所有的導(dǎo)出對象都增加一個(gè)ExportMetadata標(biāo)記,寫出如下形式:
[ExportMetadata("Name", "DB Logger")] [ExportMetadata("Priority", 3)]
當(dāng)屬性更多一點(diǎn)的話相信程序員們就會罵娘了。并且一旦某個(gè)Export對象漏寫了,改對象就不會被導(dǎo)入。這個(gè)是一個(gè)運(yùn)行時(shí)的錯(cuò)誤,非常不容易排查的。
通過Attribute標(biāo)記
這種方式可以通過一個(gè)Attribute來標(biāo)記元數(shù)據(jù):
[MetadataAttribute] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] class LoggerDataAttribute : Attribute, ILoggerData { public string Name { get; private set; } public LoggerDataAttribute(string name) { this.Name = name; } } [LoggerData("Console Logger")] [Export(typeof(ILogger))] class ConsoleLogger : ILogger, ILoggerData { public string Name { get; set; } public void Log(string message) { Console.WriteLine(message); } } [LoggerData("DB Logger")] [Export(typeof(ILogger))] class DbLogger : ILogger, ILoggerData { public string Name { get; set; } public void Log(string message) { Console.WriteLine(message); } }
首先,聲明一個(gè)LoggerDataAttribute,這個(gè)Attribute必須被MetadataAttribute標(biāo)記。然后,在Export的對象前加上該LoggerDataAttribute,這樣MEF導(dǎo)入的時(shí)候就會根據(jù)該LoggerDataAttribute創(chuàng)建元數(shù)據(jù)了。
值得一提的是,這里的LoggerDataAttribute本身并不需要實(shí)現(xiàn)ILoggerData接口,它是一個(gè)DuckType的約定,只需要實(shí)現(xiàn)元數(shù)據(jù)的屬性即可。我這里實(shí)現(xiàn)該接口主要是為了讓編譯器保障元數(shù)據(jù)屬性都有被準(zhǔn)確實(shí)現(xiàn)。
到此這篇關(guān)于C#在MEF框架中實(shí)現(xiàn)延遲加載部件的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#實(shí)現(xiàn)的json序列化和反序列化代碼實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)的json序列化和反序列化代碼實(shí)例,本文講解了兩種實(shí)現(xiàn)方法,并直接給出代碼示例,需要的朋友可以參考下2015-06-06C#實(shí)現(xiàn)注冊碼注冊機(jī)制效果詳解
這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)注冊碼注冊機(jī)制效果,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2023-01-01C#多線程學(xué)習(xí)之Thread、ThreadPool、Task、Parallel四者區(qū)別
這篇文章主要以一些簡單的小例子,簡述多線程的發(fā)展歷程:Thread,ThreadPool,Task,Parallel。文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#多線程有一定幫助,需要的朋友可以參考一下2021-12-12C#實(shí)現(xiàn)定時(shí)任務(wù)Task Scheduler的示例代碼
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)定時(shí)任務(wù)Task Scheduler的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02