检索.NET 应用中的资源

使用 .NET 应用中的本地化资源时,最好用主程序集打包默认或非特定区域性的资源,并为应用支持的每种语言或区域性单独创建附属程序集。 可以使用下一节中介绍的 ResourceManager 类访问已命名的资源。 如果选择不在主程序集和附属程序集中嵌入资源,也可以按本文后面的从 .resources 文件中检索资源一节中所述直接访问二进制 .resources 文件。

从程序集中检索资源

ResourceManager 类提供对运行时资源的访问权限。 使用 ResourceManager.GetString 方法检索字符串资源和 ResourceManager.GetObject 或使用 ResourceManager.GetStream 方法检索非字符串资源。 每个方法都有两种重载:

资源管理器使用资源回退进程控制应用检索区域性特定资源的方式。 有关详细信息,请参阅打包和部署资源中的“资源回退进程”一节。 有关实例化 ResourceManager 对象的详细信息,请参阅 ResourceManager 类主题中的“实例化 ResourceManager 对象”一节。

检索字符串数据示例

下面的示例调用 GetString(String) 方法检索当前 UI 区域性的字符串资源。 它包括英语(美国)区域性的非特定字符串资源和法语(法国)和俄语(俄罗斯)区域性的本地化资源。 下面的英语(美国)资源位于名为 Strings.txt 的文件中:

TimeHeader=The current time is

法语(法国)资源位于名为 Strings.fr-FR.txt 的文件中:

TimeHeader=L'heure actuelle est

俄语(俄罗斯)资源位于名为 Strings.ru-RU.txt 的文件中:

TimeHeader=Текущее время —

此示例的源代码(在代码的 C# 版本中位于名为 GetString.cs 的文件中,在 Visual Basic 版本中位于名为 GetString.vb 的文件中)定义包含四种区域性名称(资源可用的三种区域性和西班牙语(西班牙)区域性)的字符串数组。 一个随机执行 5 次的循环选择其中一种区域性并将其分配到 Thread.CurrentCultureCultureInfo.CurrentUICulture 属性。 然后调用 GetString(String) 方法检索与一天当中的时间一起显示的本地化的字符串。

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

[assembly: NeutralResourcesLanguageAttribute("en-US")]

public class Example
{
   public static void Main()
   {
      string[] cultureNames = { "en-US", "fr-FR", "ru-RU", "es-ES" };
      Random rnd = new Random();
      ResourceManager rm = new ResourceManager("Strings",
                               typeof(Example).Assembly);

      for (int ctr = 0; ctr <= cultureNames.Length; ctr++) {
         string cultureName = cultureNames[rnd.Next(0, cultureNames.Length)];
         CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureName);
         Thread.CurrentThread.CurrentCulture = culture;
         Thread.CurrentThread.CurrentUICulture = culture;

         Console.WriteLine("Current culture: {0}", culture.NativeName);
         string timeString = rm.GetString("TimeHeader");
         Console.WriteLine("{0} {1:T}\n", timeString, DateTime.Now);
      }
   }
}
// The example displays output like the following:
//    Current culture: English (United States)
//    The current time is 9:34:18 AM
//
//    Current culture: Español (España, alfabetización internacional)
//    The current time is 9:34:18
//
//    Current culture: русский (Россия)
//    Текущее время — 9:34:18
//
//    Current culture: français (France)
//    L'heure actuelle est 09:34:18
//
//    Current culture: русский (Россия)
//    Текущее время — 9:34:18
Imports System.Globalization
Imports System.Resources
Imports System.Threading

<Assembly: NeutralResourcesLanguageAttribute("en-US")>

Module Example
    Public Sub Main()
        Dim cultureNames() As String = {"en-US", "fr-FR", "ru-RU", "es-ES"}
        Dim rnd As New Random()
        Dim rm As New ResourceManager("Strings", GetType(Example).Assembly)

        For ctr As Integer = 0 To cultureNames.Length
            Dim cultureName As String = cultureNames(rnd.Next(0, cultureNames.Length))
            Dim culture As CultureInfo = CultureInfo.CreateSpecificCulture(cultureName)
            Thread.CurrentThread.CurrentCulture = culture
            Thread.CurrentThread.CurrentUICulture = culture

            Console.WriteLine("Current culture: {0}", culture.NativeName)
            Dim timeString As String = rm.GetString("TimeHeader")
            Console.WriteLine("{0} {1:T}", timeString, Date.Now)
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output similar to the following:
'    Current culture: English (United States)
'    The current time is 9:34:18 AM
'    
'    Current culture: Español (España, alfabetización internacional)
'    The current time is 9:34:18
'    
'    Current culture: русский (Россия)
'    Текущее время — 9:34:18
'    
'    Current culture: français (France)
'    L'heure actuelle est 09:34:18
'    
'    Current culture: русский (Россия)
'    Текущее время — 9:34:18

以下批处理 (.bat) 文件编译该示例,并在相应的目录中生成附属程序集。 为 C# 语言和编译器提供了命令。 对于 Visual Basic ,将 csc 更改为 vbc,并将 GetString.cs 更改为 GetString.vb

resgen strings.txt
csc GetString.cs -resource:strings.resources

resgen strings.fr-FR.txt
md fr-FR
al -embed:strings.fr-FR.resources -culture:fr-FR -out:fr-FR\GetString.resources.dll

resgen strings.ru-RU.txt
md ru-RU
al -embed:strings.ru-RU.resources -culture:ru-RU -out:ru-RU\GetString.resources.dll

当前 UI 区域性为西班牙语(西班牙)时,请注意该示例会显示英语语言资源,因为西班牙语语言资源不可用,而英语是该示例的默认区域性。

检索对象数据示例

可以使用 GetObjectGetStream 方法检索对象数据。 这包括基元数据类型、可序列化对象和以二进制格式存储的对象(如图像)。

下面的示例使用 GetStream(String) 方法检索应用启动初始窗口中使用的位图。 以下源代码位于名为 CreateResources.cs 的文件中(C# 版本)或位于名为 CreateResources.vb 的文件中(Visual Basic 版本),它能生成包含序列化图像的 .resx 文件。 在这种情况下,图片从一个名为 SplashScreen.jpg 的文件中加载;可以修改文件名以替换你自己的图像。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Resources;

public class Example
{
   public static void Main()
   {
      Bitmap bmp = new Bitmap(@".\SplashScreen.jpg");
      MemoryStream imageStream = new MemoryStream();
      bmp.Save(imageStream, ImageFormat.Jpeg);

      ResXResourceWriter writer = new ResXResourceWriter("AppResources.resx");
      writer.AddResource("SplashScreen", imageStream);
      writer.Generate();
      writer.Close();
   }
}
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Resources

Module Example
    Public Sub Main()
        Dim bmp As New Bitmap(".\SplashScreen.jpg")
        Dim imageStream As New MemoryStream()
        bmp.Save(imageStream, ImageFormat.Jpeg)

        Dim writer As New ResXResourceWriter("AppResources.resx")
        writer.AddResource("SplashScreen", imageStream)
        writer.Generate()
        writer.Close()
    End Sub
End Module

以下代码将检索该资源,并在 PictureBox 控件中显示图像。

using System;
using System.Drawing;
using System.IO;
using System.Resources;
using System.Windows.Forms;

public class Example
{
   public static void Main()
   {
      ResourceManager rm = new ResourceManager("AppResources", typeof(Example).Assembly);
      Bitmap screen = (Bitmap) Image.FromStream(rm.GetStream("SplashScreen"));

      Form frm = new Form();
      frm.Size = new Size(300, 300);

      PictureBox pic = new PictureBox();
      pic.Bounds = frm.RestoreBounds;
      pic.BorderStyle = BorderStyle.Fixed3D;
      pic.Image = screen;
      pic.SizeMode = PictureBoxSizeMode.StretchImage;

      frm.Controls.Add(pic);
      pic.Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
                   AnchorStyles.Left | AnchorStyles.Right;

      frm.ShowDialog();
   }
}
Imports System.Drawing
Imports System.IO
Imports System.Resources
Imports System.Windows.Forms

Module Example
    Public Sub Main()
        Dim rm As New ResourceManager("AppResources", GetType(Example).Assembly)
        Dim screen As Bitmap = CType(Image.FromStream(rm.GetStream("SplashScreen")), Bitmap)

        Dim frm As New Form()
        frm.Size = new Size(300, 300)

        Dim pic As New PictureBox()
        pic.Bounds = frm.RestoreBounds
        pic.BorderStyle = BorderStyle.Fixed3D
        pic.Image = screen
        pic.SizeMode = PictureBoxSizeMode.StretchImage

        frm.Controls.Add(pic)
        pic.Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or
                     AnchorStyles.Left Or AnchorStyles.Right

        frm.ShowDialog()
    End Sub
End Module

可以使用以下批处理文件生成 C# 示例。 对于 Visual Basic,将 csc 更改为 vbc,并将源代码文件的扩展名由 .cs 更改为 .vb

csc CreateResources.cs
CreateResources

resgen AppResources.resx

csc GetStream.cs -resource:AppResources.resources

下面的示例使用 ResourceManager.GetObject(String) 方法反序列化一个自定义对象。 该示例包含一个名为 UIElements.cs (对于 Visual Basic 则为 UIElements.vb)的源代码文件,用于定义以下名为 PersonTable的结构。 此结构应由显示表列的本地化名称的常规表显示例程使用。 请注意, PersonTable 结构标有 SerializableAttribute 属性。

using System;

[Serializable] public struct PersonTable
{
   public readonly int nColumns;
   public readonly string column1;
   public readonly string column2;
   public readonly string column3;
   public readonly int width1;
   public readonly int width2;
   public readonly int width3;

   public PersonTable(string column1, string column2, string column3,
                  int width1, int width2, int width3)
   {
      this.column1 = column1;
      this.column2 = column2;
      this.column3 = column3;
      this.width1 = width1;
      this.width2 = width2;
      this.width3 = width3;
      this.nColumns = typeof(PersonTable).GetFields().Length / 2;
   }
}
<Serializable> Public Structure PersonTable
    Public ReadOnly nColumns As Integer
    Public Readonly column1 As String
    Public ReadOnly column2 As String
    Public ReadOnly column3 As String
    Public ReadOnly width1 As Integer
    Public ReadOnly width2 As Integer
    Public ReadOnly width3 As Integer

    Public Sub New(column1 As String, column2 As String, column3 As String,
                   width1 As Integer, width2 As Integer, width3 As Integer)
        Me.column1 = column1
        Me.column2 = column2
        Me.column3 = column3
        Me.width1 = width1
        Me.width2 = width2
        Me.width3 = width3
        Me.nColumns = Me.GetType().GetFields().Count \ 2
    End Sub
End Structure

下面的代码来自名为 CreateResources.cs(对于 Visual Basic 则为 CreateResources.vb)的文件,该代码创建一个名为 UIResources.resx 的 XML 资源文件,该文件存储有表标题和包含已针对英语语言本地化的应用的信息的 PersonTable 对象。

using System;
using System.Resources;

public class CreateResource
{
   public static void Main()
   {
      PersonTable table = new PersonTable("Name", "Employee Number",
                                          "Age", 30, 18, 5);
      ResXResourceWriter rr = new ResXResourceWriter(@".\UIResources.resx");
      rr.AddResource("TableName", "Employees of Acme Corporation");
      rr.AddResource("Employees", table);
      rr.Generate();
      rr.Close();
   }
}
Imports System.Resources

Module CreateResource
    Public Sub Main()
        Dim table As New PersonTable("Name", "Employee Number", "Age", 30, 18, 5)
        Dim rr As New ResXResourceWriter(".\UIResources.resx")
        rr.AddResource("TableName", "Employees of Acme Corporation")
        rr.AddResource("Employees", table)
        rr.Generate()
        rr.Close()
    End Sub
End Module

下面的代码位于名为 GetObject.cs (GetObject.vb) 的源代码文件中,然后检索资源并将其显示在控制台上。

using System;
using System.Resources;

[assembly: NeutralResourcesLanguageAttribute("en")]

public class Example
{
   public static void Main()
   {
      string fmtString = String.Empty;
      ResourceManager rm = new ResourceManager("UIResources", typeof(Example).Assembly);
      string title = rm.GetString("TableName");
      PersonTable tableInfo = (PersonTable) rm.GetObject("Employees");

      if (! String.IsNullOrEmpty(title)) {
         fmtString = "{0," + ((Console.WindowWidth + title.Length) / 2).ToString() + "}";
         Console.WriteLine(fmtString, title);
         Console.WriteLine();
      }

      for (int ctr = 1; ctr <= tableInfo.nColumns; ctr++) {
         string columnName = "column"  + ctr.ToString();
         string widthName = "width" + ctr.ToString();
         string value = tableInfo.GetType().GetField(columnName).GetValue(tableInfo).ToString();
         int width = (int) tableInfo.GetType().GetField(widthName).GetValue(tableInfo);
         fmtString = "{0,-" + width.ToString() + "}";
         Console.Write(fmtString, value);
      }
      Console.WriteLine();
   }
}
Imports System.Resources

<Assembly: NeutralResourcesLanguageAttribute("en")>

Module Example
    Public Sub Main()
        Dim fmtString As String = String.Empty
        Dim rm As New ResourceManager("UIResources", GetType(Example).Assembly)
        Dim title As String = rm.GetString("TableName")
        Dim tableInfo As PersonTable = DirectCast(rm.GetObject("Employees"), PersonTable)

        If Not String.IsNullOrEmpty(title) Then
            fmtString = "{0," + ((Console.WindowWidth + title.Length) \ 2).ToString() + "}"
            Console.WriteLine(fmtString, title)
            Console.WriteLine()
        End If

        For ctr As Integer = 1 To tableInfo.nColumns
            Dim columnName As String = "column" + ctr.ToString()
            Dim widthName As String = "width" + ctr.ToString()
            Dim value As String = CStr(tableInfo.GetType().GetField(columnName).GetValue(tableInfo))
            Dim width As Integer = CInt(tableInfo.GetType().GetField(widthName).GetValue(tableInfo))
            fmtString = "{0,-" + width.ToString() + "}"
            Console.Write(fmtString, value)
        Next
        Console.WriteLine()
    End Sub
End Module

可以生成必要的资源文件和程序集,并通过执行以下批处理文件运行该应用。 必须使用 /r 选项提供具有对 UIElements.dll 的引用的 Resgen.exe,以便其能够访问有关 PersonTable 结构的信息。 如果使用 C#,请将 vbc 编译器名称替换为 csc,并将 .vb 扩展名替换为 .cs

vbc -t:library UIElements.vb
vbc CreateResources.vb -r:UIElements.dll
CreateResources

resgen UIResources.resx  -r:UIElements.dll
vbc GetObject.vb -r:UIElements.dll -resource:UIResources.resources

GetObject.exe

附属程序集的版本支持

默认情况下, ResourceManager 对象检索请求的资源时,会寻找版本号与主程序集版本号相匹配的附属程序集。 部署应用后,建议更新主程序集或特定资源附属程序集。 .NET Framework 提供对主程序集和附属程序集的版本控制支持。

SatelliteContractVersionAttribute 特性提供对主程序集的版本控制支持。 在应用的主程序集上指定此属性,无需更新主程序集的附属程序集即可更新和重新部署主程序集。 更新主程序集后,递增主程序集的版本号,但附属协定版本号保持不变。 资源管理器检索请求的资源时,会加载此属性指定的附属程序集版本。

发行者策略程序集提供对附属程序集的版本控制支持。 你可以更新并重新部署附属程序集,而不用更新主程序集。 更新附属程序集后,递增其版本号,并将其附带到发行者策略程序集中。 在发行者策略程序集中,指定新附属程序集为后向兼容其之前版本。 资源管理器会使用 SatelliteContractVersionAttribute 属性确定附属程序集的版本,但程序集加载程序将绑定到发行者策略所指定的附属程序集版本。 有关发行者策略程序集的详细信息,请参阅 创建发行者策略文件

若要启用完全的程序集版本控制支持,建议你在 全局程序集缓存 中部署具有强名称的程序集,并将不具有强名称的程序集部署在应用程序目录中。 若在应用程序目录中部署具有强名称的程序集,则无法在更新程序集时递增附属程序集的版本号。 相反,必须在使用更新的代码替换现有代码处执行就地更新,并保持相同的版本号。 例如,若要使用完全指定的程序集名称 "myApp.resources, Version=1.0.0.0, Culture=de, PublicKeyToken=b03f5f11d50a3a" 更新版本 1.0.0.0 的附属程序集,请使用已编译同一个完全指定的程序集名称 "myApp.resources, Version=1.0.0.0, Culture=de, PublicKeyToken=b03f5f11d50a3a" 的更新的 myApp.resources.dll 来覆盖它。 请注意,在附属程序集文件上使用就地更新会使应用难以准确确定附属程序集的版本。

有关程序集版本控制的详细信息,请参阅 程序集版本控制运行时如何定位程序集

从 .resources 文件中检索资源

如果选择不在附属程序集中部署资源,你仍可以使用 ResourceManager 对象直接访问 .resources 文件中的资源。 要执行此操作,必须正确部署.resources 文件。 然后使用 ResourceManager.CreateFileBasedResourceManager 方法实例化 ResourceManager 对象,并指定包含独立 .resources 文件的目录。

部署 .resources 文件

将 .resources 文件嵌入应用程序程序集和附属程序集后,每个附属程序集都具有相同的文件名,但被放在反射附属程序集区域性的子目录中。 与此相反,从 .resources 文件直接访问资源时,可以将所有 .resources 文件放在单一目录(通常为应用程序目录的子目录)中。 应用的默认 .resources 文件名称仅包含一个根名称,不带有其区域性的指示(例如 strings.resources)。 每个本地化的区域性资源存储在名称包含根名称,后带有区域性标记所组成的文件中(例如 strings.ja.resources 或 strings.de-DE.resources)。

下图显示资源文件应被放置在目录结构中的何处。 它还提供了.resource 文件的命名约定。

显示应用程序主目录的插图。

使用资源管理器

创建资源并将其放置在相应的目录中后,通过调用 ResourceManager 方法创建 CreateFileBasedResourceManager(String, String, Type) 对象以使用资源。 第一个参数指定应用的默认 .resources 文件的根名称(在上一节的示例中为 "strings")。 第二个参数指定的资源的位置(上一个示例中为 "Resources")。 第三个参数指定要使用的 ResourceSet 实现。 如果第三个参数为 null,则使用默认运行时 ResourceSet

注意

请勿使用独立 .resources 文件部署 ASP.NET 应用。 这可能会导致锁定问题并破坏 XCOPY 部署。 建议部署附属程序集中的 ASP.NET 资源。 有关更多信息,请参见 ASP.NET Web Page Resources Overview

实例化 ResourceManager 对象后,使用前文所述的 GetStringGetObjectGetStream 方法检索资源。 但是,直接从 .resources 文件中检索资源与从程序集中检索嵌入的资源有所不同。 从 .resources 文件中检索资源时, GetString(String)GetObject(String)GetStream(String) 方法总是忽略当前区域性检索默认区域性的资源。 若要检索应用的当前区域性资源或指定区域性的资源,必须调用 GetString(String, CultureInfo)GetObject(String, CultureInfo)GetStream(String, CultureInfo) 方法并指定要检索资源的区域性。 若要检索当前区域性的资源,请将 CultureInfo.CurrentCulture 属性的值指定为 culture 参数。 如果资源管理器无法检索 culture的资源,则使用标准资源回退规则检索相应的资源。

示例

下面的示例说明资源管理器如何直接从 .resources 文件中检索资源。 此示例由三个基于文本的资源文件组成,区域性分别为英语(美国)、法语(法国)和俄语(俄罗斯)。 英语(美国)为示例的默认区域性。 其资源存储在以下名为 Strings.txt 的文件中:

Greeting=Hello
Prompt=What is your name?

法语(法国) 区域性的资源存储在以下名为 Strings.fr-FR.txt 的文件中:

Greeting=Bon jour
Prompt=Comment vous appelez-vous?

俄语(俄罗斯) 区域性的资源存储在以下名为 Strings.ru-RU.txt 的文件中:

Greeting=Здравствуйте
Prompt=Как вас зовут?

以下是该实例的源代码。 该示例为英语(美国)、英语(加拿大)、法语(法国)和俄语(俄罗斯)区域性实例化 CultureInfo 对象,并将以上每一种语言作为当前区域性。 ResourceManager.GetString(String, CultureInfo) 方法提供 CultureInfo.CurrentCulture 属性的值作为 culture 参数来检索相应的区域性指定资源。

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

[assembly: NeutralResourcesLanguage("en-US")]

public class Example
{
   public static void Main()
   {
      string[] cultureNames = { "en-US", "en-CA", "ru-RU", "fr-FR" };
      ResourceManager rm = ResourceManager.CreateFileBasedResourceManager("Strings", "Resources", null);

      foreach (var cultureName in cultureNames) {
         Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
         string greeting = rm.GetString("Greeting", CultureInfo.CurrentCulture);
         Console.WriteLine("\n{0}!", greeting);
         Console.Write(rm.GetString("Prompt", CultureInfo.CurrentCulture));
         string name = Console.ReadLine();
         if (! String.IsNullOrEmpty(name))
            Console.WriteLine("{0}, {1}!", greeting, name);
      }
      Console.WriteLine();
   }
}
// The example displays output like the following:
//       Hello!
//       What is your name? Dakota
//       Hello, Dakota!
//
//       Hello!
//       What is your name? Koani
//       Hello, Koani!
//
//       Здравствуйте!
//       Как вас зовут?Samuel
//       Здравствуйте, Samuel!
//
//       Bon jour!
//       Comment vous appelez-vous?Yiska
//       Bon jour, Yiska!
Imports System.Globalization
Imports System.Resources
Imports System.Threading

<Assembly: NeutralResourcesLanguageAttribute("en-US")>

Module Example
    Public Sub Main()
        Dim cultureNames() As String = {"en-US", "en-CA", "ru-RU", "fr-FR"}
        Dim rm As ResourceManager = ResourceManager.CreateFileBasedResourceManager("Strings", "Resources", Nothing)

        For Each cultureName In cultureNames
            Console.WriteLine()
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
            Dim greeting As String = rm.GetString("Greeting", CultureInfo.CurrentCulture)
            Console.WriteLine("{0}!", greeting)
            Console.Write(rm.GetString("Prompt", CultureInfo.CurrentCulture))
            Dim name As String = Console.ReadLine()
            If Not String.IsNullOrEmpty(name) Then
                Console.WriteLine("{0}, {1}!", greeting, name)
            End If
        Next
        Console.WriteLine()
    End Sub
End Module
' The example displays output like the following:
'       Hello!
'       What is your name? Dakota
'       Hello, Dakota!
'       
'       Hello!
'       What is your name? Koani
'       Hello, Koani!
'       
'       Здравствуйте!
'       Как вас зовут?Samuel
'       Здравствуйте, Samuel!
'       
'       Bon jour!
'       Comment vous appelez-vous?Yiska
'       Bon jour, Yiska!

可以通过运行以下批处理文件编译该示例的 C# 版本。 如果使用 Visual Basic,请将 csc 替换为 vbc,并将 .cs 扩展名替换为 .vb

md Resources
resgen Strings.txt Resources\Strings.resources
resgen Strings.fr-FR.txt Resources\Strings.fr-FR.resources
resgen Strings.ru-RU.txt Resources\Strings.ru-RU.resources

csc Example.cs

请参阅