C# Resources资源详解
作者:路灯下的诗人
1. Resource Basics
(1) Manifest Resources(资源清单)
资源在编译期间添加到程序集。如果要将资源嵌入到程序集,则必须将文件添加到项目中,文件会自动拷贝到项目文件夹的Resources文件夹中。如果要嵌入到程序集,还需选中文件,修改其属性“生成操作”(Build Action)为“嵌入的资源”,默认为“内容”。
一旦设置为嵌入的资源,则它就会成为资源清单中程序集的一部分。每一程序集,无论是静态的还是动态的,均包含描述该程序集中各元素彼此如何关联的数据集合。程序集清单就包含这些程序集元数据。程序集清单包含指定该程序集的版本要求和安全标识所需的所有元数据,以及定义该程序集的范围和解析对资源和类的引用所需的全部元数据。
(2) Naming Mainfest Resources
要查看一个已经正确嵌入到项目输出程序集中的文件,可以利用SDK工具 ildasm.exe,它其实就是 MSIL 反汇编程序,它能够在你程序集的Mainfest视图里显示所有的嵌入资源。
它在ildasm中显示为 .mresource 入口,资源名称显示格式如下:
defaultNamespace.folderName.fileName
defaultNamespace 可以在项目的属性页面中“应用程序”Tab页面中进行更改。
(3) Loading Mainfest Resources
也通过程序来枚举清单,需要利用到 System.Reflection.Assembly 类的 GetMainifestResourceNames 方法。另外,还可以检索特定类型的程序集,例如 Assembly 类还提供了 GetAssembly、GetCallingAssembly、GetEntryAssembly 和 GetExecutingAssembly 等。
// Get this type's assembly Assembly asm =this.GetType().Assembly; // Enumerate the assembly's manifest resources foreach( string resourceName in asm.GetManifestResourceNames() ){ MessageBox.Show(resourceName); }
Type 为 System.Reflection 功能的根,也是访问元数据的主要方式。使用 Type 的成员获取关于类型声明的信息,如构造函数、方法、字段、属性 (Property) 和类的事件,以及在其中部署该类的模块和程序集。
表示某个类型是唯一的 Type 对象;即,两个 Type 对象引用当且仅当它们表示相同的类型时,才引用相同的对象。这允许使用参考等式来比较 Type 对象。Type 类表示类型声明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。Object.GetType 方法返回表示实例类型的 Type 对象。
如果知道资源的名称,则可以通过 Assembly 类的方法 GetManifestResourceStream 方法加载指定的清单资源,资源名称大小写敏感,而且是全称。例如:
// Get this type's assembly Assembly asm =this.GetType().Assembly; // Get the stream that holds the resource // from the "ResourcesSample.Azul.jpg" resource // NOTE1: Make sure not to close this stream, // or the Bitmap object will lose access to it // NOTE2: Also be very careful to match the case // on the resource name itself Stream stream = asm.GetManifestResourceStream("ResourcesSample.Azul.jpg"); // Load the bitmap from the stream this.BackgroundImage =new Bitmap(stream);
(4) Mainfest Resource Namespaces
如果传递给 GetMainfestResourcesStream 方法 一个System.Type 对象,则它会用此类型的命名空间当作嵌入资源的前缀的一部分,如:
// Load the stream for resource "ResourcesSample.Azul.jpg" Stream stream = asm.GetManifestResourceStream(this.GetType(), "Azul.jpg");
Bitmap 类也可以直接装载嵌入的资源,如:
// Load image from "ResourcesApp.Azul.jpg" this.BackgroundImage =new Bitmap(this.GetType(), "Azul.jpg");
2. Strongly Typed Resources
在清单资源中不包含类型信息,虽然文件带有扩展名,但是类似Bitmap类是根据数据本身来判断类型的。
(1) Application Resources(.resx) Files
资源文件(.resx)的主要作用就是记录资源的相应类型信息,由于采用了.NET特定的XML方案(ResX)来保存资源类型信息。一个空的 .resx 文件也有42行内容,大多数都是方案信息。这个方案允许有许多条目信息,每个都包含name、value, comment, type 和 Multipurpose Internet Mail Extensions(MIME) type。虽然此文件是基于文本的,但是如果要编辑或者阅读,最好还是通过VS2005的资源编辑器来完成。
.resx 格式中的名称/值对在 XML 代码中打包,它描述字符串或对象值。当字符串被添加到 .resx 文件中时,该字符串的名称被嵌入在 <data> 标记中,并且值包括在 <value> 标记内,如以下示例所示。
当一个对象被插入到 .resx 文件中时,使用相同的 <data> 和 <value> 标记来描述该项,但 <data> 标记要包括类型或 MIME 类型说明符。类型说明符保留所保存对象的数据类型。如果对象由二进制数据组成,则 MIME 类型说明符保持所存储的二进制信息的基类型 (Base64)。
(2) Managing Resources
资源编辑器支持六类资源:
a. 字符串 : 在带有字符串资源的“名称”、“值”和“注释”列的设置网格中显示字符串。
b. 图像 : 显示所有图像文件(包括 .bmp、.jpg 和 .gif 格式)。这些文件在运行时作为 Bitmap 公开。此类别也包括作为 Metafile 公开的 Windows 图元文件。
c. 图标
d. 音频 : 显示声音文件(包括 .wav、.wma 和 .mp3 文件)。这些文件作为字节数组公开。
e. 文件 : 显示不适合以上类别的任何文件。此视图中的项是作为 String 公开的文本文件,或是作为字节数组公开的二进制文件。
f. 其他 : 显示用来添加其他支持字符串序列化的类型(例如,Font、Enum、Color 和 Point)的设置网格。此网格包含以下列:“名称”、“类型”、“值”和“注释”。
添加资源:
可以通过拖放、菜单选择来添加资源,通过资源编辑器添加到.resx文件的所有资源文件都会放到项目文件夹下的Resources文件夹下,如果不存在则创建。如果将一个已经存在项目里的资源添加到.resx文件中,则这个资源不会被移动或拷贝到Resources文件夹下,因为资源编辑器是通过对文件的引用来管理资源文件,resx文件仅仅存放实际清单资源的类型信息。
删除资源:
通过资源编辑器只能从.resx中移除或者剪切资源,而不能实际删除,因为你只是对资源的元数据进行操作,而并非真正的资源文件(字符串除外,它只能被嵌入)。如果从项目中删除资源文件,但是.resx中的相应元数据还在,这样在编译时就会出现编译异常。
编辑资源: 可以通过资源编辑器直接打开相应的编辑程序;
(3) Resource Persistance
链接资源作为文件存储在项目中;在编译期间,从这些文件中取得资源数据,并将其放到应用程序的清单中。应用程序的资源文件 (.resx) 只存储指向磁盘上的文件的相对路径或链接。对于嵌入资源,资源数据直接以二进制数据的文本表示形式存储在 .resx 文件中。在任何一种情况下,资源数据都将编译到可执行文件中。
注意点:字符串资源总是嵌入的资源,无法更改;文件资源总是链接的资源,也无法更改。
如何在嵌入的资源和链接的资源之间进行选择?
-----------------------------------------------------------------
在多数情况下,应该坚持默认的链接资源。但是,在有些情况下选择嵌入的资源会更好。
嵌入的资源:
如果需要在多个项目之间共享应用程序资源 (.resx) 文件,则嵌入的资源是最佳选择。例如,如果您有一个包含公司徽标、商标信息等类似内容的通用资源文件,则应使用嵌入的资源,这样您只需复制 .resx 文件,而不用复制关联的资源数据文件。不能直接编辑嵌入的资源。如果试图编辑嵌入的资源,您将会接收到一条消息,提示您将该项转换为链接的资源以便对其进行编辑;此转换是可选的,但建议进行转换。必须导出它们并在外部程序中进行修改,然后将其导回项目中。
链接的资源:
就易用性而言,链接的资源(默认值)是最好的选择。可以在项目内部直接编辑资源,并且可以根据需要轻松添加或移除资源。
-----------------------------------------------------------------
可以通过指定资源的Persistance属性来决定。如果将属性改为嵌入后,将资源文件删除并不会有什么影响,此时如果再改回链接方式,则会在Resources目录下重新创建此资源文件。
如果在资源编辑器下对资源进行管理,则会发现资源的BuildAction,即生成操作都会变为“无”。其实Persistance属性仅仅在设计期间起作用,最终资源数据都将编译到可执行文件中。
(4) Using Typed Resources
* 直接使用.resx文件
利用 ResXResourceReader 类来访问.resx 文件;
此类可以枚举 XML 资源 (.resx) 文件和流,并读取顺序资源名称和值对,存在于命名空间 System.Resources。如果要提取特定条目,则需要先进行遍历寻找。
using( ResXResourceReader reader = new ResXResourceReader(@"C:\MyResources.resx") ) { foreach( DictionaryEntry entry in reader ) { string s =string.Format("{0} ({1})= '{2}'", entry.Key, entry.Value.GetType(), entry.Value); MessageBox.Show(s); } }
* 使用编译的.resx 资源
您可以采用三种不同的方式创建资源文件。如果您的资源将只包含字符串数据,则最简单的方法是手动创建文本文件。如果您的资源将包含对象或字符串与对象的组合,则您必须创建 .resx 文件或 .resources 文件。只有.resources 文件才应嵌入在公共语言运行库程序集和附属程序集中。资源文件生成器 (Resgen.exe) 将文本 (.txt) 文件和基于 XML 的资源 (.resx) 文件转换成 .resources 文件,
资源生成器 Resgen.exe :
将 .txt 文件转换为 .resources 文件,方法是包装由 ResourceWriter 类实现的方法。Resgen.exe 还包装 ResourceReader,这使您可以使用该工具来将 .resources 文件转换回 .txt 文件。
编译项目后会将.resx数据嵌入为嵌套资源放在资源清单,例如项目缺省命名空间为A,rest文件为B.resx,则这个嵌套资源容器则为 A.B.resources,可以在ildasm中查看。对它的读取利用 ResourceReader 类,也不支持随机访问。
using( ResourceReader reader = new ResourceReader("MyResources.resources") ) { foreach( DictionaryEntry entry in reader ) { string s =string.Format("{0} ({1})= '{2}'", entry.Key, entry.Value.GetType(), entry.Value); MessageBox.Show(s); } }
其实还可以直接通过对清单资源流的访问来操作 .resources 文件。
Assembly asm = Assembly.GetExecutingAssembly(); // Load embedded .resources file using( Stream stream = asm.GetManifestResourceStream( this.GetType(), "MyResources.resources") ) { // Find resource in .resources stream using( ResourceReader reader =new ResourceReader(stream) ) { foreach( DictionaryEntry entry in reader ) { if( entry.Key.ToString() =="MyString" ) { // Display string resource value MessageBox.Show("MyString = "+ (string)entry.Value); break; } } } }
以上都需要进行多步操作而且不支持随机访问,但是.NET提供了 ResourceManager 类来支持对资源的随机访问。
(5) Resource Manager
ResourceManager 类可以查找区域性特定的资源,当本地化资源不存在时提供代用资源,并支持资源序列化,它其实也就是对ResourceReader的封装。它由一个嵌入.resource文件初始化:
Assembly asm =this.GetType().Assembly; ResourceManager resman =new ResourceManager("ResourcesSample.MyResources", asm);
通过 ResourceManager 的方法,调用方可使用 GetObject 和 GetString 两种方法访问特定区域性的资源。
// Load ResourcesSample.MainForm.resources from MainForm.resx ResourceManager resman =new ResourceManager(this.GetType()); // Access the MyString string resource from the ResourceManager // (these two techniques are equivalent for strings) string s1 = (string)resman.GetObject("MyString"); string s2 = resman.GetString("MyString");
(6) 强类型资源类
Resource Manager 提供了对资源的弱类型方法GetObject来返回资源,需要进行类型转换。但是VS2005和一个自定义工具 ResXFileCodeGenerator 提供了对这个问题的解决办法。当一个 .resx 文件被保存时,VS2005会应用自定义工具将其产生一个相应的 .Designer.cs 文件,此文件提供了一个名字和 .resx 文件相同的类,这个类所处的命名空间为 defaultNamespace.projectPath。
namespace ResourcesSample { ///<summary> /// A strongly typed resource class, for looking up localized /// strings, etc. ///</summary> // This class was autogenerated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .resx file and then rerun ResGen // with the /str option, or rebuild your VS project. internalclass MyResources { static global::System.Resources.ResourceManager resourceMan; static global::System.Globalization.CultureInfo resourceCulture; internal MyResources() {} ///<summary> /// Returns the cached ResourceManager instance used by this /// class. ///</summary> internalstatic global:: System.Resources.ResourceManager ResourceManager { get { if( (resourceMan ==null) ) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager( "ResourcesSample.MyResources", typeof(MyResources).Assembly); resourceMan = temp; } return resourceMan; } } ///<summary> /// Overrides the current thread's CurrentUICulture property for /// all resource lookups using this strongly typed resource class. ///</summary> internalstatic global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } } }
由以上可以看出 MyResource 类型的两个特征:
a. 它通过一个ResourceManager类型的属性提供了对ResourceManager的静态访问,就没必要写之前的创建逻辑了; b. 通过一个Culture属性提供了CultureInfo对象实现对本地化信息的静态访问;
提供的每个资源都是强类型静态只读属性。在内部实现中,每个属性都由设计器生成的资源类利用托管的ResourceMananger对象产生的;
// Access strongly typed resources from MyResources.resx string myString = MyResources.MyString; Icon myIcon = MyResources.MyIcon;
(7) Designer Resources
VS2005提供了整个项目的资源管理。由于Resources.resx 文件是从项目的属性页来进行管理的,所以 VS2005将它存放到项目的Properties文件夹中。当在项目中添加.resx文件时,ResXFileCodeGenerator工具会自动生成Resources.Designer.cs:
namespace ResourcesSample.Properties {
...
internal class Resources {
...
则可以如下来访问资源:MessageBox.Show(Properties.Resources.MyString);
项目会自动生成一个跟窗体关联的.resx 文件,保存类似 BackgroundImage 和 Icon 等资源信息。属性窗口可以打开选择资源编辑器,允许选择适当的图片资源。它提供了两种导入和保存资源的方式:作为本地资源;作为项目资源文件。对于窗体,本地资源被嵌入到窗体设计器自动创建的.resx文件中并和窗体关联,如果选择本地资源,则可以直接将图片导入到窗体的资源文件中。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。