以编程方式使用 .resx 文件

更新:2010 年 12 月

由于 XML 资源 (.resx) 文件必须包含定义完善的 XML(包括一个必须位于后跟名称/值对形式的数据的特定架构后面的标头),您可能会发现手动创建这些文件很容易出错。 或者,可以使用 .NET Framework 类库中的类型和成员以编程方式创建 .resx 文件。 还可以使用 .NET Framework 类库检索存储在 .resx 文件中的资源。 本主题说明如何使用 System.Resources 命名空间中的类型和成员来处理 .resx 文件。

警告说明警告

除了编程方式之外,还可通过其他方式处理 .resx 文件。在向 Visual Studio 项目添加资源文件时,Visual Studio 会提供一个用于创建和维护 .resx 文件的接口,并会在编译时自动将 .resx 文件转换为 .resources 文件。您还可以使用文本编辑器直接操作 .resx 文件。但为了避免破坏该文件,操纵文件时要小心,不要修改存储在该文件中的任何二进制信息。

创建 .resx 文件

通过按以下步骤进行操作,可以使用 System.Resources.ResXResourceWriter 类以编程方式创建 .resx 文件:

  1. 通过调用 ResXResourceWriter.ResXResourceWriter(String) 方法并提供 .resx 文件的名称,可实例化 ResXResourceWriter 对象。 文件名必须包含 .resx 扩展名。 如果您在 using 块中实例化 ResXResourceWriter 对象,则无需在步骤 3 中显式调用 ResXResourceWriter.Close 方法。

  2. 对要添加到文件中的各项资源调用 ResXResourceWriter.AddResource 方法。 使用此方法的重载以添加字符串、对象和二进制(字节数组)数据。 如果资源是一个对象,则它必须是可序列化的。

  3. 调用 ResXResourceWriter.Close 方法以生成资源文件并释放所有资源。 如果已在 using 块中创建 ResXResourceWriter 对象,则会将资源写入 .resx 文件中,并会在 using 块的末尾释放 ResXResourceWriter 对象所使用的资源。

生成的 .resx 文件包含相应的标头,并且 ResXResourceWriter.AddResource 方法所添加的每项资源均有一个 data 标记。

警告说明警告

不要使用资源文件来存储密码、安全敏感信息或保密数据。

下面的示例创建了一个名为 CarResources.resx 的 .resx 文件,该文件存储了六个字符串、一个图标和两个应用程序定义的对象(两个 Automobile 对象)。 请注意,该示例中定义和实例化的 Automobile 类用 SerializableAttribute 特性进行标记。

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
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);  
      }
   }
}
重要说明重要事项

还可以使用 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 属性值。

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
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

检索特定资源

除了枚举 .resx 文件中的项之外,还可以使用 System.Resources.ResXResourceSet 类按名称检索特定资源。 ResourceSet.GetString(String) 方法检索命名字符串资源的值。 ResourceSet.GetObject(String) 方法检索命名对象或二进制数据的值。 该方法返回一个对象,该对象稍后必须被强制转换(在 C# 中)或转换(在 Visual Basic 中)为相应类型的对象。

下面的示例按窗体的标题字符串和图标的资源名称检查它们。 它还检索上一示例中使用的应用程序定义的 Automobile 对象,并在 DataGridView 控件中显示这些对象。

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
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;
      }
   }
}

将 .resx 文件转换为二进制 .resources 文件

将 .resx 文件转换为嵌入式二进制资源 (.resources) 文件能带来明显的好处。 虽然 .resx 文件在应用程序开发期间易于读取和维护,但这些文件很少包含在完成的应用程序中。 如果这些文件随应用程序一起分发,则它们将作为独立于应用程序可执行文件及其附带库的单独文件存在。 相比之下,.resources 文件将嵌入应用程序可执行文件或其附带程序集中。 另外,对于在运行时依赖 .resx 文件的本地化应用程序,开发人员负责处理资源回退。 相比之下,如果已创建一组包含嵌入式 .resources 文件的附属程序集,则公共语言运行时将处理资源回退进程。

若要将一个 .resx 文件转换为一个 .resources 文件,请使用 资源文件生成器 (Resgen.exe)(它包含以下基本语法):

Resgen.exe .resxFilename

这会生成一个具有与 .resx 文件相同的根文件名的二进制资源文件,且该文件的扩展名为 .resources。 然后,可在编译时将该文件编译为可执行文件或库。 如果您使用的是 Visual Basic 编译器,则使用以下语法以在应用程序的可执行文件中嵌入一个 .resources 文件:

vbc filename**.vb /resource:**.resxFilename

如果您使用的是 C#,则语法如下所示:

csc filename**.cs /resource:**.resxFilename

还可以使用具有以下基本语法的程序集链接器 (AL.exe) 将 .resources 文件嵌入附属程序集中:

al resxFilename **/out:**assemblyFilename

请参见

参考

Resgen.exe(资源文件生成器)

Al.exe(程序集链接器)

概念

创建资源文件

修订记录

日期

修订记录

原因

2010 年 12 月

新增主题。

信息补充。