注释
本文适用于 .NET Framework。 有关适用于 .NET 5+ 的信息(包括 .NET Core),请参阅 .resx 文件中的资源。
由于 XML 资源 (.resx) 文件必须包含结构完善的 XML,其中标头必须遵循特定的架构,并且后面是名称/值对的数据,因此你可能会发现手动创建这些文件容易出错。 或者,可以使用 .NET 类库中的类型和成员以编程方式创建 .resx 文件。 还可以使用 .NET 类库检索存储在 .resx 文件中的资源。 本文介绍如何使用命名空间中的 System.Resources 类型和成员来处理 .resx 文件。
本文讨论使用包含资源的 XML (.resx) 文件。 有关使用已嵌入程序集的二进制资源文件的信息,请参阅 ResourceManager。
警告
除了以编程方式,还有其他方法可以处理 .resx 文件。 将资源文件添加到 Visual Studio 项目时,Visual Studio 提供了用于创建和维护 .resx 文件的接口,并在编译时自动将 .resx 文件转换为 .resources 文件。 还可以使用文本编辑器直接编辑 .resx 文件。 但是,为了避免损坏文件,请注意不要修改存储在文件中的任何二进制信息。
创建 .resx 文件
可以按照以下步骤使用 System.Resources.ResXResourceWriter 类以编程方式创建 .resx 文件:
通过调用ResXResourceWriter方法并提供 .resx 文件的名称来实例化ResXResourceWriter(String)对象。 文件名必须包含 .resx 扩展名。 如果在ResXResourceWriter块中实例化
using
对象,则不必显式调用步骤 3 中的ResXResourceWriter.Close方法。为每个要添加到此文件中的资源调用 ResXResourceWriter.AddResource 方法。 使用方法的重载来添加字符串、对象,以及二进制(字节数组)数据。 如果资源是对象,则它必须可序列化。
ResXResourceWriter.Close调用该方法以生成资源文件并释放所有资源。 如果ResXResourceWriter对象是在
using
块内创建的,资源将写入 .resx 文件,由ResXResourceWriter对象使用的资源会在using
块末尾释放。
生成的 .resx 文件具有相应的标头,并且每个由 data
方法添加的资源都有一个 ResXResourceWriter.AddResource 标记。
警告
请勿使用资源文件存储密码、 安全敏感信息或私人数据。
以下示例创建一个名为 CarResources.resx 的 .resx 文件,该文件存储六个字符串、一个图标和两个应用程序定义的对象(两 Automobile
个对象)。
Automobile
类在示例中被定义和实例化,并附带 SerializableAttribute 属性。
using System;
using System.Drawing;
using System.Resources;
[Serializable()] public class Automobile
{
private string carMake;
private string carModel;
private int carYear;
private int carDoors;
private int carCylinders;
public Automobile(string make, string model, int year) :
this(make, model, year, 0, 0)
{ }
public Automobile(string make, string model, int year,
int doors, int cylinders)
{
this.carMake = make;
this.carModel = model;
this.carYear = year;
this.carDoors = doors;
this.carCylinders = cylinders;
}
public string Make {
get { return this.carMake; }
}
public string Model {
get {return this.carModel; }
}
public int Year {
get { return this.carYear; }
}
public int Doors {
get { return this.carDoors; }
}
public int Cylinders {
get { return this.carCylinders; }
}
}
public class Example
{
public static void Main()
{
// Instantiate an Automobile object.
Automobile car1 = new Automobile("Ford", "Model N", 1906, 0, 4);
Automobile car2 = new Automobile("Ford", "Model T", 1909, 2, 4);
// Define a resource file named CarResources.resx.
using (ResXResourceWriter resx = new ResXResourceWriter(@".\CarResources.resx"))
{
resx.AddResource("Title", "Classic American Cars");
resx.AddResource("HeaderString1", "Make");
resx.AddResource("HeaderString2", "Model");
resx.AddResource("HeaderString3", "Year");
resx.AddResource("HeaderString4", "Doors");
resx.AddResource("HeaderString5", "Cylinders");
resx.AddResource("Information", SystemIcons.Information);
resx.AddResource("EarlyAuto1", car1);
resx.AddResource("EarlyAuto2", car2);
}
}
}
Imports System.Drawing
Imports System.Resources
<Serializable()> Public Class Automobile
Private carMake As String
Private carModel As String
Private carYear As Integer
Private carDoors AS Integer
Private carCylinders As Integer
Public Sub New(make As String, model As String, year As Integer)
Me.New(make, model, year, 0, 0)
End Sub
Public Sub New(make As String, model As String, year As Integer,
doors As Integer, cylinders As Integer)
Me.carMake = make
Me.carModel = model
Me.carYear = year
Me.carDoors = doors
Me.carCylinders = cylinders
End Sub
Public ReadOnly Property Make As String
Get
Return Me.carMake
End Get
End Property
Public ReadOnly Property Model As String
Get
Return Me.carModel
End Get
End Property
Public ReadOnly Property Year As Integer
Get
Return Me.carYear
End Get
End Property
Public ReadOnly Property Doors As Integer
Get
Return Me.carDoors
End Get
End Property
Public ReadOnly Property Cylinders As Integer
Get
Return Me.carCylinders
End Get
End Property
End Class
Module Example
Public Sub Main()
' Instantiate an Automobile object.
Dim car1 As New Automobile("Ford", "Model N", 1906, 0, 4)
Dim car2 As New Automobile("Ford", "Model T", 1909, 2, 4)
' Define a resource file named CarResources.resx.
Using resx As New ResXResourceWriter(".\CarResources.resx")
resx.AddResource("Title", "Classic American Cars")
resx.AddResource("HeaderString1", "Make")
resx.AddResource("HeaderString2", "Model")
resx.AddResource("HeaderString3", "Year")
resx.AddResource("HeaderString4", "Doors")
resx.AddResource("HeaderString5", "Cylinders")
resx.AddResource("Information", SystemIcons.Information)
resx.AddResource("EarlyAuto1", car1)
resx.AddResource("EarlyAuto2", car2)
End Using
End Sub
End Module
小窍门
还可以使用 Visual Studio 创建 .resx 文件。 在编译时,Visual Studio 使用 资源文件生成器(Resgen.exe) 将 .resx 文件转换为二进制资源(.resources)文件,并在应用程序程序集或附属程序集中嵌入该文件。
不能在运行时可执行文件中嵌入 .resx 文件,也不能将其编译为附属程序集。 必须使用 资源文件生成器(Resgen.exe)将 .resx 文件转换为二进制资源(.resources)文件。 然后,生成的 .resources 文件可以嵌入到应用程序程序集或卫星程序集中。 有关详细信息,请参阅 “创建资源文件”。
枚举资源
在某些情况下,可能需要从 .resx 文件检索所有资源,而不是特定资源。 为此,可以使用该 System.Resources.ResXResourceReader 类,该类为 .resx 文件中的所有资源提供枚举器。 类 System.Resources.ResXResourceReader 实现了 IDictionaryEnumerator,它返回一个 DictionaryEntry 对象,该对象表示循环每次迭代的特定资源。 其 DictionaryEntry.Key 属性返回资源的键,其 DictionaryEntry.Value 属性返回资源的值。
以下示例为上一示例中创建的 CarResources.resx 文件创建一个 ResXResourceReader 对象,并循环访问资源文件。 它将资源文件中定义的两 Automobile
个对象添加到对象,并将六个 System.Collections.Generic.List<T> 字符串中的五个添加到对象 SortedList 。 对象中的 SortedList 值将转换为参数数组,该数组用于向控制台显示列标题。 属性值 Automobile
也会显示在控制台中。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Resources;
public class Example
{
public static void Main()
{
string resxFile = @".\CarResources.resx";
List<Automobile> autos = new List<Automobile>();
SortedList headers = new SortedList();
using (ResXResourceReader resxReader = new ResXResourceReader(resxFile))
{
foreach (DictionaryEntry entry in resxReader) {
if (((string) entry.Key).StartsWith("EarlyAuto"))
autos.Add((Automobile) entry.Value);
else if (((string) entry.Key).StartsWith("Header"))
headers.Add((string) entry.Key, (string) entry.Value);
}
}
string[] headerColumns = new string[headers.Count];
headers.GetValueList().CopyTo(headerColumns, 0);
Console.WriteLine("{0,-8} {1,-10} {2,-4} {3,-5} {4,-9}\n",
headerColumns);
foreach (var auto in autos)
Console.WriteLine("{0,-8} {1,-10} {2,4} {3,5} {4,9}",
auto.Make, auto.Model, auto.Year,
auto.Doors, auto.Cylinders);
}
}
// The example displays the following output:
// Make Model Year Doors Cylinders
//
// Ford Model N 1906 0 4
// Ford Model T 1909 2 4
Imports System.Collections
Imports System.Collections.Generic
Imports System.Resources
Module Example
Public Sub Main()
Dim resxFile As String = ".\CarResources.resx"
Dim autos As New List(Of Automobile)
Dim headers As New SortedList()
Using resxReader As New ResXResourceReader(resxFile)
For Each entry As DictionaryEntry In resxReader
If CType(entry.Key, String).StartsWith("EarlyAuto") Then
autos.Add(CType(entry.Value, Automobile))
Else If CType(entry.Key, String).StartsWith("Header") Then
headers.Add(CType(entry.Key, String), CType(entry.Value, String))
End If
Next
End Using
Dim headerColumns(headers.Count - 1) As String
headers.GetValueList().CopyTo(headerColumns, 0)
Console.WriteLine("{0,-8} {1,-10} {2,-4} {3,-5} {4,-9}",
headerColumns)
Console.WriteLine()
For Each auto In autos
Console.WriteLine("{0,-8} {1,-10} {2,4} {3,5} {4,9}",
auto.Make, auto.Model, auto.Year,
auto.Doors, auto.Cylinders)
Next
End Sub
End Module
' The example displays the following output:
' Make Model Year Doors Cylinders
'
' Ford Model N 1906 0 4
' Ford Model T 1909 2 4
检索特定资源
除了枚举 .resx 文件中的项之外,还可以使用 System.Resources.ResXResourceSet 类按名称检索特定资源。 该方法 ResourceSet.GetString(String) 检索命名字符串资源的值。 该方法 ResourceSet.GetObject(String) 检索命名对象或二进制数据的值。 该方法返回的对象之后必须转换为(C# 中的 cast 或 Visual Basic 中的 convert)相应类型的对象。
以下示例按其资源名称检索窗体的标题字符串和图标。 它还检索在上一Automobile
个示例中使用的应用程序定义DataGridView对象,并在控件中显示它们。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Resources;
using System.Windows.Forms;
public class CarDisplayApp : Form
{
private const string resxFile = @".\CarResources.resx";
Automobile[] cars;
public static void Main()
{
CarDisplayApp app = new CarDisplayApp();
Application.Run(app);
}
public CarDisplayApp()
{
// Instantiate controls.
PictureBox pictureBox = new PictureBox();
pictureBox.Location = new Point(10, 10);
this.Controls.Add(pictureBox);
DataGridView grid = new DataGridView();
grid.Location = new Point(10, 60);
this.Controls.Add(grid);
// Get resources from .resx file.
using (ResXResourceSet resxSet = new ResXResourceSet(resxFile))
{
// Retrieve the string resource for the title.
this.Text = resxSet.GetString("Title");
// Retrieve the image.
Icon image = (Icon) resxSet.GetObject("Information", true);
if (image != null)
pictureBox.Image = image.ToBitmap();
// Retrieve Automobile objects.
List<Automobile> carList = new List<Automobile>();
string resName = "EarlyAuto";
Automobile auto;
int ctr = 1;
do {
auto = (Automobile) resxSet.GetObject(resName + ctr.ToString());
ctr++;
if (auto != null)
carList.Add(auto);
} while (auto != null);
cars = carList.ToArray();
grid.DataSource = cars;
}
}
}
Imports System.Collections.Generic
Imports System.Drawing
Imports System.Resources
Imports System.Windows.Forms
Public Class CarDisplayApp : Inherits Form
Private Const resxFile As String = ".\CarResources.resx"
Dim cars() As Automobile
Public Shared Sub Main()
Dim app As New CarDisplayApp()
Application.Run(app)
End Sub
Public Sub New()
' Instantiate controls.
Dim pictureBox As New PictureBox()
pictureBox.Location = New Point(10, 10)
Me.Controls.Add(pictureBox)
Dim grid As New DataGridView()
grid.Location = New Point(10, 60)
Me.Controls.Add(grid)
' Get resources from .resx file.
Using resxSet As New ResXResourceSet(resxFile)
' Retrieve the string resource for the title.
Me.Text = resxSet.GetString("Title")
' Retrieve the image.
Dim image As Icon = CType(resxSet.GetObject("Information", True), Icon)
If image IsNot Nothing Then
pictureBox.Image = image.ToBitmap()
End If
' Retrieve Automobile objects.
Dim carList As New List(Of Automobile)
Dim resName As String = "EarlyAuto"
Dim auto As Automobile
Dim ctr As Integer = 1
Do
auto = CType(resxSet.GetObject(resName + ctr.ToString()), Automobile)
ctr += 1
If auto IsNot Nothing Then carList.Add(auto)
Loop While auto IsNot Nothing
cars = carList.ToArray()
grid.DataSource = cars
End Using
End Sub
End Class
将 .resx 文件转换为二进制 .resources 文件
将 .resx 文件转换为嵌入的二进制资源(.resources)文件具有显著优势。 尽管 .resx 文件在应用程序开发过程中易于读取和维护,但它们很少包含在完成的应用程序中。 如果它们与应用程序一起分发,则它们作为单独的文件存在,除了应用程序可执行文件及其随附的库。 相比之下, .resources 文件嵌入应用程序可执行文件或其随附的程序集中。 此外,对于本地化应用程序,在运行时依赖 .resx 文件时,开发人员负责处理资源回退问题。 相比之下,如果已创建一组包含嵌入式 .resources 文件的卫星程序集,则公共语言运行库将处理资源回退过程。
若要将 .resx 文件转换为 .resources 文件,请使用 资源文件生成器(resgen.exe),该生成器具有以下基本语法:
resgen.exe .resxFilename
结果是一个二进制资源文件,该文件的根文件名与 .resx 文件和 .resources 文件扩展名相同。 然后,可以在编译时将此文件编译为可执行文件或库。 如果使用 Visual Basic 编译器,请使用以下语法在应用程序的可执行文件中嵌入 .resources 文件:
vbc filename .vb -resource: .resourcesFilename
如果使用 C#,语法如下所示:
csc filename .cs -resource: .resourcesFilename
还可以使用程序集链接器(al.exe)将 .resources 文件嵌入到卫星程序集,基本语法如下:
al resourcesFilename -out: assemblyFilename