.NET的资源并不限于.resx文件,你可以采用任意存储形式[上篇] (转载)
為了構(gòu)建一個輕量級的資源管理框架以滿足簡單的本地化(Localization)的需求,我試圖直接對現(xiàn)有的Resource編程模型進(jìn)行擴(kuò)展。雖然最終沒能滿足我們的需求,但是這兩天也算對.NET如何進(jìn)行資源的存取進(jìn)行了深入的學(xué)習(xí),所以將我對此的認(rèn)識通過博文的方式與諸位分享。在本篇文章中,我會通過自定義ResourceManager讓資源的存儲形式不僅僅局限于.ResX文件,你可以根據(jù)需要實現(xiàn)任意的存儲方式,比如結(jié)構(gòu)化的XML、數(shù)據(jù)庫表,甚至是通過遠(yuǎn)程訪問獲取資源。(文中的例子從這里下載)
一、從添加資源文件說起
二、ResourceManager、ResourceSet、ResourceReader與ResourceWriter
三、自定義BinaryResourceManager管理單獨二機(jī)制資源文件
一、從添加資源文件(.resx文件)說起
說起資源,你首先想到的肯定是通過VS添加的擴(kuò)展名為.resx的資源文件。在這個資源文件中,你不但可以添加單純的文本資源條目,也可以添加圖片、圖標(biāo)、文本文件以及其它類型文件。 不但如此,當(dāng)你在.resx文件中定義任意類型資源條目的時候,默認(rèn)定義的代碼生成器會為你生成對應(yīng)的托管代碼,使你可以采用強類型編程的方式獲取某個條目。
比如說,如果你在一個名稱為Resources.resx的資源文件中定義了如上圖所示的兩個字符串資源條目,默認(rèn)的代碼生成器或為你生成如下的代碼。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } ? [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Demo.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } ? [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } ? internal static string Greeting4Chris { get { return ResourceManager.GetString("Greeting4Chris", resourceCulture); } } internal static string Greeting4NewYear { get { return ResourceManager.GetString("Greeting4NewYear", resourceCulture); } } }那么你就可以通過生成的這個Resources類(和資源文件同名)的對應(yīng)的靜態(tài)只讀屬性獲取對應(yīng)的值。
1: var greeting4Chris = Resources.Greeting4Chris; 2: var greeting4NewYear = Resources.Greeting4NewYear;從通過代碼生成器生成出來的Resources代碼,我們可以看出Greeting4Chris和Greeting4NewYear這兩個屬性的實現(xiàn)是直接通過一個類型為ResourceManager對象的GetString方法獲取的。那么ResourceManager在背后是通過怎樣的機(jī)制進(jìn)行資源文件的讀取的呢?
二、ResourceManager、ResourceSet、ResourceReader與ResourceWriter
ResourceManager應(yīng)該是.NET資源編程模型的核心,也可以說是整個資源編程模型的外觀類(Facade Class),它提供資源條目提取的API。ResourceManager定義在System.Resources命名空間下,我們不防先來看看ResourceManager的定義。
1: public class ResourceManager 2: { 3: public ResourceManager(Type resourceSource); 4: public ResourceManager(string baseName, Assembly assembly); 5: public ResourceManager(string baseName, Assembly assembly, Type usingResourceSet); 6:? 7: public virtual object GetObject(string name); 8: public virtual object GetObject(string name, CultureInfo culture); 9: public virtual string GetString(string name); 10: public virtual string GetString(string name, CultureInfo culture); 11:? 12: public virtual ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents); 13: protected virtual ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents); 14: //Others... 15: }雖然我們將相應(yīng)的條目定義在.resx資源文件中(該文件實際上就是一個XML),但是該文件在編譯的時候會變成.resources文件(二進(jìn)制文件)被內(nèi)嵌到程序集中,所以ResourceManager操作的實際上是內(nèi)嵌在某個程序集中的.resources文件,這也是為什么在構(gòu)造函數(shù)中需要指定Assembly的原因。構(gòu)造函數(shù)的另一個參數(shù)BaseName表示不包括擴(kuò)展名和Culture Code的.resources文件名,比如說資源文件名為Foo.en-US.resoures對應(yīng)的BaseName就是Foo。
對于字符串類型的資源條目,通過GetString方法獲取,其他類型的文件則通過GetObject獲取。而ResourceManager的核心實際上是一個叫做GetResourceSet的方法,方法將所有的資源條目讀取出來保存到一個類型為ResourceSet的對象中(該方法最終會調(diào)用受保護(hù)的方法InternalGetResourceSet)。而ResourceSet在整個資源體系中是一個重要的對象,它充當(dāng)ResourceManager和物理存儲的中介,下面是ResourceSet的定義。
1: public class ResourceSet : IDisposable, IEnumerable 2: { 3: public ResourceSet(Stream stream); 4: public ResourceSet(IResourceReader reader); 5: public ResourceSet(string fileName); 6: 7: public virtual Type GetDefaultReader(); 8: public virtual Type GetDefaultWriter(); 9:? 10: public virtual object GetObject(string name); 11: public virtual object GetObject(string name, bool ignoreCase); 12: public virtual string GetString(string name); 13: public virtual string GetString(string name, bool ignoreCase); 14:? 15: IEnumerator IEnumerable.GetEnumerator(); 16: public virtual IDictionaryEnumerator GetEnumerator(); 17: public void Dispose(); 18: //Others... 19: }以持久化文件方式存儲的資源最終需要加載到ResourceSet對象中,肯定需要IO操作,所以ResourceSet構(gòu)造函數(shù)中參數(shù)分別是Stream、文件名和一個IResourceReader的對象。GetObject和GetString方法,不用多說你也知道是用于某個命名資源條目。由于資源條目實際上就是簡單Key-Value對,所以ResourceSet僅僅需要為ResourceManager提供針對每個資源條目的迭代功能,所以ResourceSet的核心應(yīng)該是返回類型為IDictionaryEnumerator虛方法GetEmunerator方法。
而ResourceSet得兩個GetDefaultReader和GetDefaultWriter方法則涉及到另外兩個重要的對象ResourceReader和ResourceWriter,故名思義它們分別負(fù)責(zé)資源的讀取和寫入。在System.Resources命名空間下,它們各自具有相應(yīng)的接口:IResourceReader和IResourceWriter,定義如下:
1: public interface IResourceReader : IEnumerable, IDisposable 2: { 3: void Close(); 4: IDictionaryEnumerator GetEnumerator(); 5: }?
1: public interface IResourceWriter : IDisposable 2: { 3: void AddResource(string name, object value); 4: void AddResource(string name, string value); 5: void AddResource(string name, byte[] value); 6: void Close(); 7: void Generate(); 8: }到這里我們介紹了資源體系下四個重要的對象ResourceManager、ResourceSet、ResourceReader與ResourceWriter,至于他們是如何相互協(xié)作以實現(xiàn)對資源的讀取和寫入的,相信下面會給你答案。
三、自定義BinaryResourceManager管理單獨二進(jìn)制資源文件(.resources文件)
我們說過上述的ResourceManager僅僅提供對內(nèi)嵌于某個程序集中的.resources文件的操作,如果我們直接將資源定義在一個獨立的.resources文件、.resx文件甚至是自定義結(jié)構(gòu)的XML文件呢?在這種情況下,我們可通過自定義ResourceManager的方式來解決這個問題。為此我定義了如下一個抽象類FileResourceManager作為基于文件的ResourceManager的基類。
1: public abstract class FileResourceManager: ResourceManager 2: { 3: private string baseName; 4: public string Directory { get; private set; } 5: public string Extension { get; private set; } 6:? 7: public override string BaseName 8: { 9: get{ return baseName;} 10: } 11:? 12: public FileResourceManager(string directory, string baseName, string extension) 13: { 14: this.Directory = directory; 15: this.baseName = baseName; 16: this.Extension = extension; 17: } 18:? 19: protected override string GetResourceFileName(CultureInfo culture) 20: { 21: string fileName = string.Format("{0}.{1}.{2}", this.baseName, culture, this.Extension.TrimStart('.')); 22: string path = Path.Combine(this.Directory, fileName); 23: if (File.Exists(path)) 24: { 25: return path; 26: } 27: return Path.Combine(this.Directory, string.Format("{0}.{1}", baseName, this.Extension.TrimStart('.'))); 28: } 29: }屬性Directory、BaseName和Extension分別表示資源文件所在的目錄、不包括Culture Code和擴(kuò)展名的文件名以及擴(kuò)展名。FileResourceManager集成自ResourceManager類,并重寫了GetResourceFileName方法用于獲取基于某種Culture的資源文件路徑。
現(xiàn)在我們定義如下一個BinaryResourceManager用于操作單獨存在的.resources文件。我自需要重寫InternalGetResourceSet,返回的是基于.resources文件名創(chuàng)建的ResourceSet對象。
1: public class BinaryResourceManager : FileResourceManager 2: { 3: public BinaryResourceManager(string directory, string baseName) 4: : base(directory, baseName, ".resources") 5: {} 6:? 7: protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents) 8: { 9: return new ResourceSet(this.GetResourceFileName(culture)); 10: } 11: }現(xiàn)在我們來看看如何使用我們創(chuàng)建的BinaryResourceManager。由于它直接操作ResourceSet來維護(hù)資源條目列表,當(dāng)我們通過指定資源文件名創(chuàng)建ResourceSet的時候,系統(tǒng)會創(chuàng)建一個類型為System.Resources.ResourceReader的對象來讀取二進(jìn)制的.resources文件并將內(nèi)容寫入ResourceSet對象。而.resources文件具有默認(rèn)的ResourceWrtier,即System.Resources.ResourceWriter。
為了讓我們的Demo能夠適用于后續(xù)的自定義ResourceManager,我寫了一些輔助方法,首先是預(yù)先創(chuàng)建資源文件的方法PrepareFiles方法。通過傳入的BaseName和擴(kuò)展名,我會創(chuàng)建三個資源文件:<BaseName>.<Extension>、<BaseName>.en-US.<Extension>和<BaseName>.zh-CN.<Extension>,第一個代碼語言文化中性,后者則基于美國英語和簡體中文。
1: static void PrepareFiles(string baseName, string extension) 2: { 3: var fileNames = new string[]{ 4: baseName + "." + extension, 5: baseName + ".en-US." + extension, 6: baseName + ".zh-CN." + extension }; 7:? 8: Array.ForEach(fileNames, fileName =>{ 9: if (!File.Exists(fileName)) File.Create(fileName).Dispose();}); 10: }然后是用于資源寫入操作的AddResource方法,該方法兩個參數(shù)createWriter和culture表示創(chuàng)建IResourceWriter的委托和對應(yīng)的語言文化。
1: static void AddResource(Func<IResourceWriter> createWriter, CultureInfo culture) 2: { 3: using (IResourceWriter resourceWriter = createWriter()) 4: { 5: if (culture.Name.StartsWith("en")) 6: { 7: resourceWriter.AddResource("Greeting4Chris", "Merry Christmas!"); 8: resourceWriter.AddResource("Greeting4NewYear", "Happy Chinese New Year!"); 9: } 10: if (culture.Name.StartsWith("zh")) 11: { 12: resourceWriter.AddResource("Greeting4Chris", "圣誕快樂!"); 13: resourceWriter.AddResource("Greeting4NewYear", "新年快樂!"); 14: } 15: resourceWriter.Generate(); 16: } 17: }最后是用于資源讀取和輸出的DisplayResource方法,該方法通過指定的ResourceManager讀取當(dāng)前需要文化資源并輸出。而我指定了三種不同的語言文化環(huán)境:en-US、zh-CN和ja-JP。
1: static void DisplayResource(ResourceManager resourceManager) 2: { 3: Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); 4: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName); 5: Console.WriteLine("\t"+resourceManager.GetString("Greeting4Chris")); 6: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n"); 7:? 8: Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN"); 9: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName); 10: Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris")); 11: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n"); 12:? 13: Thread.CurrentThread.CurrentUICulture = new CultureInfo("ja-JP"); 14: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName); 15: Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris")); 16: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n"); 17: }最后我們的程序是這樣的:
1: PrepareFiles("GreetingMessages", "resources"); 2:? 3: AddResource(() => new ResourceWriter("GreetingMessages.resources"), new CultureInfo("en-US")); 4: AddResource(() => new ResourceWriter("GreetingMessages.en-US.resources"), new CultureInfo("en-US")); 5: AddResource(() => new ResourceWriter("GreetingMessages.zh-CN.resources"), new CultureInfo("zh-CN")); 6:? 7: DisplayResource(new BinaryResourceManager("", "GreetingMessages"));最終的輸出為:
1: English (United States) 2: Merry Christmas! 3: Happy Chinese New Year! 4:? 5: Chinese (Simplified, PRC) 6: 圣誕快樂! 7: 新年快樂! 8:? 9: Japanese (Japan) 10: Merry Christmas! 11: Happy Chinese New Year!在《下篇》中,我將介紹如何通過自定義ResourceManager操作定義在.resx、XML和數(shù)據(jù)庫表的資源。
轉(zhuǎn)載于:https://www.cnblogs.com/WPCTO/archive/2010/12/18/1909795.html
總結(jié)
以上是生活随笔為你收集整理的.NET的资源并不限于.resx文件,你可以采用任意存储形式[上篇] (转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 昨天、今天、明天
- 下一篇: 多线程socket 端口扫描程序,实现了