.NET的资源并“.NET研究”不限于.resx文件,你可以采用任意存储形式 [上篇]

本文详细阐述了如何通过自定义ResourceManager类,实现对多种存储形式资源的管理,以满足本地化需求。包括从资源文件的添加说起,深入探讨ResourceManager、ResourceSet、ResourceReader与ResourceWriter的工作原理,最后介绍如何自定义BinaryResourceManager来操作独立的.resources文件。通过实例代码演示了如何使用自定义的ResourceManager进行资源读取和写入。

  为了构建一个轻量级的资源管理框架以满足简单的本地化(Localization)的需求,我试图直接对现有的Resource编程模型进行扩展。虽然最终没能满足我们的需求,但是这两天也算对.NET如何进行资源的存取进行了深入的学习,所以将我对此的认识通过博文的方式与诸位分享。在本篇文章中,我会通过自定义ResourceManager让资源的存储形式不仅仅局限于.ResX文件,你可以根据需要实现任意的存储方式,比如结构化的XML、数据库表,甚至是通过远程访问获取资源。(文中的例子从这里下载)

一、从添加资源文件说起
二、ResourceManager、ResourceSet、ResourceReader与ResourceWriter
三、自定义BinaryResourceManager管理单独二机制资源文件

  一、从添加资源文件(.resx文件)说起

  说起资源,你首先想到的肯定是通过VS添加的扩展名为.resx的资源文件。在这个资源文件中,你不但可以添加单纯的文本资源条目,也可以添加图片、图标、文本文件以及其它类型文件。 不但如此,当你在.resx文件中定义任意类型资源条目的时候,默认定义的代码生成器会为你生成对应的托管代码,使你可以采用强类型编程的方式获取某个条目。

image  比如说,如果你在一个名称为Resources.resx的资源文件中定义了如上图所示的两个字符串资源条目,默认的代码生成器或为你生成如下的代码。


    
[ 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类(和资源文件同名)的对应的静态只读属性获取对应的值。


    
var greeting4Chris = Resources.Greeting4Chris;
var greeting4NewYear
= Resources.Greeting4NewYear;

  从通过代码生成器生成出来的Resources代码,我们可以看出Greeting4Chris和Greeting4NewYear这两个属性的实现是直接通过一个类型为ResourceManager对象的GetString方法获取的。那么ResourceManager在背后是通过怎样的机制进行资源文件的读取的呢?

  二、ResourceManager、ResourceSet、ResourceReader与ResourceWriter

  ResourceManager应该是.NET资源编程模型的核心,也可以说是整个资源编程模型的外观类(Facade Class),它提供资源条目提取的API。ResourceManager定义在System.Resources命名空间下,我们不防先来看看ResourceManager的定义。


    
public class ResourceManager
{
public ResourceManager(Type resourceSource);
public ResourceManager( string baseName, Assembly assembly);
public ResourceManager( string baseName, Assembly assembly, Type usingResourceSet);

public virtual object GetObject( string name);
public virtual object GetObject( string name, CultureInfo culture);
public virtual string GetString( string name);
public virtual string GetString( string name, CultureInfo culture);

public< 上海徐汇企业网站制作span style="color: #000000;"> virtual ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents);
protected virtual ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents);
// Others...
}

  虽然我们将相应的条目定义在.resx资源文件中(该文件实际上就是一个XML),但是该文件在编译的时候会变成.resources文件(二进制文件)被内嵌到程序集中,所以ResourceManager操作的实际上是内嵌在某个程序集中的.resources文件,这也是为什么在构造函数中需要指定Assembly的原因。构造函数的另一个参数BaseName表示不包括扩展名和Culture Code的.resources文件名,比如说资源文件名为Foo.en-US.resoures对应的BaseName就是Foo。

  对于字符串类型的资源条目,通过GetString方法获取,其他类型的文件则通过GetObject获取。而ResourceManager的核心实际上是一个叫做GetResourceSet的方法,方法将所有的资源条目读取出来保存到一个类型为ResourceSet的对象中(该方法最终会调用受保护的方法InternalGetResourceSet)。而ResourceSet在整个资源体系中是一个重要的对象,它充当ResourceManager和物理存储的中介,下面是ResourceSet的定义。


    
public class ResourceSet : IDisposable, IEnumerable
{
public ResourceSet(Stream stream);
public ResourceSet(IResourceReader reader);
public ResourceSet( string fileName);

public virtual Type GetDefaultReader();
public virtual Type GetDefaultWriter();

public virtual object GetObject( string name);
public virtual object GetObject( string name, bool ignoreCase);
public virtual string GetString( string name);
public virtual string GetString( string name, bool ignoreCase);

IEnumerator IEnumerable.GetEnumerator();
public virtual IDictionaryEnumerator GetEnumerator();
public void Dispose();
// Others...
}

  以持久化文件方式存储的资源最终需要加载到ResourceSet对象中,肯定需要IO操作,所以ResourceSet构造函数中参数分别是Stream、文件名和一个IResourceReader的对象。GetObject和GetString方法,不用多说你也知道是用于某个命名资源条目。由于资源条目实际上就是简单Key-Value对,所以ResourceSet仅仅需要为ResourceManager提供针对每个资源条目的迭代功能,所以ResourceSet的核心应该是返回类型为IDictionaryEnumerator虚方法GetEmunerator方法。

  而ResourceSet得两个GetDefaultReader和GetDefaultWriter方法则涉及到另外两个重要的对象ResourceReader和ResourceWriter,故名思义它们分别负责资源的读取和写入。在System.Resources命名空间下,它们各自具有相应的接口:IResourceReader和IResourceWriter,定义如下:


  
public interface IResourceReader : IEnumerable, IDisposable
{
void Close();
IDictionaryEnumerator GetEnumerator();
}

public interface IResourceWriter : IDisposable
{
void AddResource( string name, object value);
void AddResource( string name, string value)上海闵行企业网站设计与制作;
void AddResource( string name, byte [] value);
void Close();
void Generate();
}

  到这里我们介绍了资源体系下四个重要的对象ResourceManager、ResourceSet、ResourceReader与ResourceWriter,至于他们是如何相互协作以实现对资源的读取和写入的,相信下面会给你答案。

  三、自定义BinaryResourceManager管理单独二进制资源文件(.resources文件)

  我们说过上述的ResourceManager仅仅提供对内嵌于某个程序集中的.resources文件的操作,如果我们直接将资源定义在一个独立的.resources文件、.resx文件甚至是自定义结构的XML文件呢?在这种情况下,我们可通过自定义ResourceManager的方式来解决这个问题。为此我定义了如下一个抽象类FileResourceManager作为基于文件的ResourceManager的基类。


    
public abstract class FileResourceManager: ResourceManager
{
private string baseName;
public string Directory { get ; priv上海闵行企业网站制作ate set ; }
public string Extension { get ; private set ; }

public override string BaseName
{
get { return baseName;}
}

public FileResourceManager( string directory, string baseName, string extension)
{
this .Directory = directory;
this .baseName = baseName;
this .Extension = extension;
}

protected override string GetResourceFileName(CultureInfo culture)
{
string fileName = string .Format( " {0}.{1}.{2} " , this .baseName, culture, this .Extension.TrimStart( ' . ' ));
string path = Path.Combine( this .Directory, fileName);
if (File.Exists(path))
{
return path;
}
return Path.Combine( this .Directory, string .Format( " {0}.{1} " , baseName, this .Extension.TrimStart( ' . ' )));
}
}上海企业网站制作

  属性Directory、BaseName和Extension分别表示资源文件所在的目录、不包括Culture Code和扩展名的文件名以及扩展名。FileResourceManager集成自ResourceManager类,并重写了GetResourceFileName方法用于获取基于某种Culture的资源文件路径。

  现在我们定义如下一个BinaryResourceManager用于操作单独存在的.resources文件。我自需要重写InternalGetResourceSet,返回的是基于.resources文件名创建的ResourceSet对象。


    
public class BinaryResourceManager : FileResourceManager
{
public BinaryResourceManager( string directory, string baseName)
:
base (directory, baseName, " .resources " )
{}

protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
{
return new ResourceSet( this .GetResourceFileName(culture));
}
}

  现在我们来看看如何使用我们创建的BinaryResourceManager。由于它直接操作ResourceSet来维护资源条目列表,当我们通过指定资源文件名创建ResourceSet的时候,系统会创建一个类型为System.Resources.ResourceReader的对象来读取二进制的.resources文件并将内容写入ResourceSet对象。而.resources文件具有默认的ResourceWrtier,即System.Resources.ResourceWriter。

  为了让我们的Demo能够适用于后续的自定义ResourceManager,我写了一些辅助方法,首先是预先创建资源文件的方法PrepareFiles方法。通过传入的BaseName和扩展名,我会创建三个资源文件:<BaseName>.<Extension>、<BaseName>.en-US.<Extension>和<BaseName>.zh-CN.<Extension>,第一个代码语言文化中性,后者则基于美国英语和简体中文。


    
static void PrepareFiles( string baseName, string extension)
{
var fileNames
= new string []{
baseName
+ " . " + extension,
baseName
+ " .en-US. " + extension,
baseName
+ " .zh-CN. " + extension };

Array.ForEach(fileNames, fileName
=> {
if ( ! File.Exists(fileName)) File.Create(fileName).Dispose();});
}

  然后是用于资源写入操作的AddResource方法,该方法两个参数createWriter和culture表示创建IResourceWriter的委托和对应的语言文化。


    
static void AddResource(Func < IResourceWriter > createWriter, CultureInfo culture)
{
using (IResourceWriter resourceWriter = createWriter())
{
if (culture.Name.StartsWith( " en " ))
{
resourceWriter.AddResource(
" Greeting4Chris " , " Merry Christmas! " );
resourceWriter.AddResource(
" Greeting4NewYear " , " Happy Chinese New Year! " );
}
if (culture.Name.StartsWith( " zh " ))
{
resourceWriter.AddResource(
" Greeting4Chris " , " 圣诞快乐! " );
resourceWriter.AddResource(
" Greeting4NewYear " , " 新年快乐! " );
}
resourceWriter.Generate();
}
}

  最后是用于资源读取和输出的DisplayResource方法,该方法通过指定的ResourceManager读取当前需要文化资源并输出。而我指定了三种不同的语言文化环境:en-US、zh-CN和ja-JP。


    
static void DisplayResource(ResourceManager resourceManager)
{
Thread.CurrentThread.CurrentUICulture
= new CultureInfo( " en-US " );
Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
Console.WriteLine(
" \t " + resourceManager.GetString( " Greeting4Chris " ));
Console.WriteLine(
" \t " + resourceManager.GetString( " Greeting4NewYear " ) + " \n " );

Thread.CurrentThread.CurrentUICulture
= new CultureInfo( " zh-CN " );
Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
Console.WriteLine(
" \t " + resourceManager.GetString( " Greeting4Chris " ));
Console.WriteLine(
" \t " + resourceManager.GetString( " Greeting4NewYear " ) + " \n " );

Thread.CurrentThread.CurrentUICulture
= new CultureInfo( " ja-JP " );
Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
Console.WriteLine(
" \t " + resourceManager.GetString( " Greeting4Chris " ));
Console.WriteLine(
" \t " + resourceManager.GetString( " Greeting4NewYear " ) + " \n " );
}

  最后我们的程序是这样的:


    
PrepareFiles( " GreetingMessages " , " resources " );
AddResource(()
=> new ResourceWriter( " GreetingMessages.resources " ), new CultureInfo( " en-US " ));
AddResource(()
=> new ResourceWriter( " GreetingMessages.en-US.resources " ), new CultureInfo( " en-US " ));
AddResource(()
=> new ResourceWriter( " GreetingMessages.zh-CN.resources " ), new CultureInfo( " zh-CN " ));
DisplayResource(
new BinaryResourceManager( "" , " GreetingMessages " ));

  最终的输出为:


    
English (United States)
Merry Christmas
!
Happy Chinese New Year
!

Chinese (Simplified, PRC)
圣诞快乐
!
新年快乐
!

Japanese (Japan)
Merry Christmas
!
Happy Chinese New Year
!

  在《下篇》中,我将介绍如何通过自定义ResourceManager操作定义在.resx、XML和数据库表的资源。

转载于:https://www.cnblogs.com/waw/archive/2011/10/18/2216986.html

/// <summary> /// 一个强类型的资源类,用于查找本地化的字符串等。 /// </summary> // 此类是由 StronglyTypedResourceBuilder // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 [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() { } /// <summary> /// 返回此类使用的缓存的 ResourceManager 实例。 /// </summary> [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("让屏幕开满玫瑰把妹必备.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// <summary> /// 使用此强类型资源类,为所有资源查找 /// 重写当前线程的 CurrentUICulture 属性。 /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } internal static System.Drawing.Bitmap 稀饭你 { get { object obj = ResourceManager.GetObject("稀饭你", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } }
.NET Core项目中添加资源文件.resx)可以通过以下步骤实现: 1. **创建资源文件**: - 在解决方案资源管理器中,右键点击你要添加资源文件文件夹。 - 选择“添加” -> “新建项”。 - 在弹出的对话框中选择“资源文件”,然后命名文件(例如:`Resources.resx`),点击“添加”。 2. **添加资源**: - 打开新创建的资源文件,你将看到一个表格界面。 - 在表格中添加你的资源项,包括名称、值和注释。 3. **访问资源**: - 在代码中,你可以通过资源类的属性来访问资源。例如,如果你有一个名为 `Resources.resx` 的资源文件,生成的类名将是 `Resources`。 - 例如: ```csharp using System; using System.Globalization; using System.Threading; using System.Windows.Forms; namespace YourNamespace { public class YourClass { public void YourMethod() { // 设置当前线程的文化信息 Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); // 访问资源文件中的资源 string message = Resources.YourResourceName; MessageBox.Show(message); } } } ``` 4. **多语言支持**: - 为不同的语言创建不同的资源文件。例如,为英语创建 `Resources.en-US.resx`,为中文创建 `Resources.zh-CN.resx`。 - 在这些资源文件中,添加相同的资源名称,但不同的值。 5. **配置资源文件**: - 在 `.csproj` 文件中,确保资源文件被正确包含。例如: ```xml <ItemGroup> <EmbeddedResource Include="Resources.resx"> <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.cs</LastGenOutput> </EmbeddedResource> <Compile Update="Resources.Designer.cs"> <DesignTime>True</DesignTime> <AutoGen>True</AutoGen> <DependentUpon>Resources.resx</DependentUpon> </Compile> </ItemGroup> ``` 通过以上步骤,你就可以在.NET Core项目中成功添加使用资源文件.resx)。这种做法不仅可以帮助你管理应用程序中的字符串和其他资源,还可以方便地进行多语言支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值