65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展的强类型资源生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (73投票s)

2006 年 4 月 18 日

CDDL

8分钟阅读

viewsIcon

856334

downloadIcon

6108

扩展版本的强类型资源生成器,支持格式化。

引言

Visual Studio .NET 2005/2008 IDE 的一项出色新功能是一个名为 ResXFileCodeGenerator 的自定义工具,每次将资源(*.resx 文件)添加到项目中时,都会自动与其关联。每当您的项目重建、保存资源文件或手动运行自定义工具时,该工具都会生成一个托管类,该类将您在 *.resx 文件中的每个资源公开为强类型的静态属性。现在,支持的任何类型的资源(包括图像、图标、字符串等)都可以轻松检索。

下面的两个屏幕截图说明了添加到 Visual Studio .NET 2005 的资源文件的默认属性,以及依赖于 Resource.resx 并由 ResXFileCodeGenerator 自定义工具自动生成的 Resource.Designer.cs 源文件。

Resource in Solution Explorer Resource properties

生成的类公开的所有属性始终是静态的。该类公开以下属性:

  • ResourceManager 属性,返回类型为 System.Resources.ResourceManager,用于在运行时访问特定于区域性的资源。
  • Culture 属性,返回类型为 System.Globalization.CultureInfo,并具有 getset 访问器。Culture 属性的 set 访问器可用于指定资源本地化的必需区域性。默认情况下,Culture 属性返回 null,这意味着文化信息是通过 CultureCurrentUICulture 属性获得的。
  • 用于资源访问的属性,名称与相应资源的名称相同(属性名称可根据所使用的代码生成器要求进行调整)。它们的类型对应于 *.resx 文件中的资源类型。

Resource editor

如果您的资源文件仅包含一个字符串资源(如上图所示),则生成的类将如下所示:

/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[GeneratedCodeAttribute("Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
[DebuggerNonUserCodeAttribute()]
[CompilerGeneratedAttribute()]
internal class Resource {
    private static ResourceManager resourceMan;
    private static CultureInfo resourceCulture;
    [SuppressMessageAttribute(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal Resource() {
    }
    /// <summary>
    ///   Returns the cached ResourceManager instance used by this class.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
    internal static ResourceManager ResourceManager {
        get {
            if (object.ReferenceEquals(resourceMan, null)) {
                ResourceManager temp =
                  new ResourceManager("MyApp.Resource", typeof(Resource).Assembly);
                resourceMan = temp;
            }
            return resourceMan;
        }
    }
    /// <summary>
    ///   Overrides the current thread's CurrentUICulture property for all
    ///   resource lookups using this strongly typed resource class.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
    internal static CultureInfo Culture {
        get {
            return resourceCulture;
        }
        set {
            resourceCulture = value;
        }
    }
    /// <summary>
    ///   Looks up a localized string similar to Message text.
    /// </summary>
    internal static string Message {
        get {
            return ResourceManager.GetString("Message", resourceCulture);
        }
    }
}

背景

尽管 ResXFileCodeGenerator 自定义工具极大地简化了资源访问过程,但我们可以指出以下四个主要缺点:

  • ResXFileCodeGenerator 自定义工具生成的强类型资源类始终具有内部可见性。由于生成的类被标记为 internal,因此除了 友好程序集 之外,无法从其他程序集访问它。但是,使用 /publicClass 选项的 resgen.exe 实用程序会将强类型资源类生成为公共类,但此时自定义工具的所有优势都将丢失。
  • 注意: Visual Studio .NET 2008 引入了一个名为 PublicResXFileCodeGenerator 的新自定义工具,用于生成公共资源类包装器。

  • 在大多数情况下,*.resx 文件仅包含字符串,包括 .NET Framework 格式化机制 使用的格式字符串(包含零个或多个格式项的字符串)。从资源加载格式字符串并为其传递正确数量的参数一直是一个问题。传递不正确的参数数量不会导致编译错误,而是会导致恼人的运行时错误。
  • ResourceManager 属性中 ResourceManager 类实例的线程不安全初始化。
  • 无法通过资源类包装器访问资源名称。
  • 生成的资源类包装器与 .NET Compact Framework 不兼容。

扩展强类型资源生成器

鉴于上述 ResXFileCodeGenerator 的缺点,我们决定开发一个扩展版本的强类型资源生成器,以弥补现有 ResXFileCodeGenerator 自定义工具的不足。

使用扩展版本的强类型资源生成器非常简单,与使用 Visual Studio .NET 2005 和 2008 附带的资源代码生成器没有区别。扩展强类型资源生成器由两个新的自定义工具表示:

  • ResXFileCodeGeneratorEx:一个生成公共资源包装器的自定义工具。
  • InternalResXFileCodeGeneratorEx:一个生成内部资源包装器的自定义工具。

首先,您必须在您的计算机上安装和注册扩展的强类型资源生成器(例如,ResXFileCodeGeneratorExInternalResXFileCodeGeneratorEx 自定义工具)。请记住,您必须拥有管理员权限才能安装和注册新的 Visual Studio .NET 自定义工具。有两种方法可以在您的计算机上注册扩展的强类型资源生成器:

  • 首选方法是下载 Windows Installer 包并将其安装到计算机上的特定位置。安装程序将自动在您的计算机上注册 ResXFileCodeGeneratorExInternalResXFileCodeGeneratorEx Visual Studio .NET 自定义工具。建议解压缩安装程序压缩包的内容并运行 Setup.exe 来启动安装。这种安装方式与 Vista 兼容。
  • 从提供的压缩包中获取源代码并重新编译它们。如果成功,ResXFileCodeGeneratorExInternalResXFileCodeGeneratorEx 自定义工具将在您的 PC 上注册。为了使工具正常工作,您必须将生成输出保留在输出目录中,因为 Visual Studio .NET 自定义工具是 COM 对象,并且应该保留在它们注册的同一目录中。

安装扩展强类型资源生成器后,您必须重新启动所有运行的 Visual Studio .NET 2005 和 2008 实例。

Resource properties with ResXFileCodeGeneratorEx custom tool

从这一点开始,您可以在您的项目中使用扩展强类型资源生成器的所有优势。您可以手动将 ResXFileCodeGeneratorExInternalResXFileCodeGeneratorEx 指定为资源文件的自定义工具,或者您可以调整默认的 Visual Studio .NET 2005/2008 项模板

让我们以 MyApp 项目为例,添加另一个包含格式化字符串的资源条目。最重要的一步是将自定义工具名称更改为扩展强类型资源生成器(ResXFileCodeGeneratorExInternalResXFileCodeGeneratorEx)。通过保存资源文件或手动运行自定义工具来运行自定义工具。您必须在 Visual Studio .NET 中右键单击资源文件,然后在下拉菜单中选择 Run Custom Tool

Resource editor with resources containing format strings

ResXFileCodeGeneratorEx 自定义工具生成的资源包装器类如下例所示:

/// <summary>
/// A strongly-typed resource class, for looking up localized strings,
/// formatting them, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilderEx class via the
// ResXFileCodeGeneratorEx custom tool. To add or remove a member, edit your .ResX file
// then rerun the ResXFileCodeGeneratorEx custom tool or rebuild your VS.NET project.
// Copyright (c) Dmytro Kryvko 2006-2008 (http://dmytro.kryvko.googlepages.com/)
[GeneratedCodeAttribute
    ("DMKSoftware.CodeGenerators.Tools.StronglyTypedResourceBuilderEx", "2.3.0.0")]
[DebuggerNonUserCodeAttribute()]
[SuppressMessageAttribute
    ("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
public partial class Resource {
    private static ResourceManager _resourceManager;
    private static object _internalSyncObject;
    private static CultureInfo _resourceCulture;
    [SuppressMessageAttribute
    ("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public Resource() {
    }
    /// <summary>
    /// Thread safe lock object used by this class.
    /// </summary>
    public static object InternalSyncObject {
        get {
            if (object.ReferenceEquals(_internalSyncObject, null)) {
                Interlocked.CompareExchange
        (ref _internalSyncObject, new object(), null);
            }
            return _internalSyncObject;
        }
    }
    /// <summary>
    /// Returns the cached ResourceManager instance used by this class.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
    public static ResourceManager ResourceManager {
        get {
            if (object.ReferenceEquals(_resourceManager, null)) {
                Monitor.Enter(InternalSyncObject);
                try {
                    if (object.ReferenceEquals(_resourceManager, null)) {
                        Interlocked.Exchange(ref _resourceManager,
                            new ResourceManager("MyApp.Resource",
                    typeof(Resource).Assembly));
                    }
                }
                finally {
                    Monitor.Exit(InternalSyncObject);
                }
            }
            return _resourceManager;
        }
    }
    /// <summary>
    /// Overrides the current thread's CurrentUICulture property for all
    /// resource lookups using this strongly typed resource class.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
    public static CultureInfo Culture {
        get {
            return _resourceCulture;
        }
        set {
            _resourceCulture = value;
        }
    }
    /// <summary>
    /// Looks up a localized string similar to 'Hello, {0}!'.
    /// </summary>
    public static string Hello {
        get {
            return ResourceManager.GetString(ResourceNames.Hello, _resourceCulture);
        }
    }
    /// <summary>
    /// Looks up a localized string similar to 'Message text'.
    /// </summary>
    public static string Message {
        get {
            return ResourceManager.GetString
        (ResourceNames.Message, _resourceCulture);
        }
    }
    /// <summary>
    /// Formats a localized string similar to 'Hello, {0}!'.
    /// </summary>
    /// <param name="arg0">An object (0) to format.</param>
    /// <returns>A copy of format string in which the format
    /// items have been replaced by the String equivalent of
    /// the corresponding instances of Object in arguments.</returns>
    public static string HelloFormat(object arg0) {
        return string.Format(_resourceCulture, Hello, arg0);
    }
    /// <summary>
    /// Lists all the resource names as constant string fields.
    /// </summary>
    public class ResourceNames {
        /// <summary>
        /// Stores the resource name 'Hello'.
        /// </summary>
        public const string Hello = "Hello";
        /// <summary>
        /// Stores the resource name 'Message'.
        /// </summary>
        public const string Message = "Message";
    }
}

正如您所见,生成的类是 public 的,这允许您在程序集之间共享资源。但是,主要区别在于添加了一个名为 HelloFormat 的附加方法。此方法是对 Hello 资源条目字符串值进行分析和验证的结果。扩展强类型资源生成器会自动确定资源字符串值是否为有效的 .NET Framework 格式字符串,并相应地生成代码。

格式方法的名称始终按以下方式生成:资源属性加上 Format 后缀。参数数量会自动计算,等于 String.Format() 方法期望的参数数量。另一方面,仍然可以通过公开的 Hello 属性获取格式字符串。如上所述,扩展强类型资源生成器会执行格式字符串验证。例如,您可能会错误地编写一个无效的格式字符串,如:Hello, {{0}。 (内部)ResXFileCodeGeneratorEx 自定义工具将解析无效格式并向您显示有关此问题的警告。在这种特定情况下,将不会生成格式方法,但资源访问属性仍将保留在生成的类中。

Resource generation warning

标准 Visual Studio 资源包装器生成器的一组小的改进:

  • 资源包装器类中没有 [CompilerGeneratedAttribute()],这使其与 .NET Compact Framework 兼容。
  • 生成嵌套类 ResourceNames,将所有资源名称定义为字符串常量。嵌套类的可见性与其父类的可见性相同。

生成公共资源类包装器几乎适合所有人,但是,有些人仍然希望能够生成内部资源包装器。因此,版本 2.1 引入了 InternalResXFileCodeGeneratorEx Visual Studio .NET 自定义工具,用于生成强类型的内部资源包装器。InternalResXFileCodeGeneratorEx 的输出如下例所示:

/// <summary>
/// A strongly-typed resource class, for looking up localized strings,
/// formatting them, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilderEx class via the
// InternalResXFileCodeGeneratorEx custom tool.
// To add or remove a member, edit your .ResX file
// then rerun the InternalResXFileCodeGeneratorEx custom tool or
// rebuild your VS.NET project.
// Copyright (c) Dmytro Kryvko 2006-2008 (http://dmytro.kryvko.googlepages.com/)
[GeneratedCodeAttribute
    ("DMKSoftware.CodeGenerators.Tools.StronglyTypedResourceBuilderEx", "2.3.0.0")]
[DebuggerNonUserCodeAttribute()]
[SuppressMessageAttribute
    ("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
internal partial class Resource {
    private static ResourceManager _resourceManager;
    private static object _internalSyncObject;
    private static CultureInfo _resourceCulture;
    [SuppressMessageAttribute
    ("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal Resource() {
    }
    /// <summary>
    /// Thread safe lock object used by this class.
    /// </summary>
    internal static object InternalSyncObject {
        get {
            if (object.ReferenceEquals(_internalSyncObject, null)) {
                Interlocked.CompareExchange
        (ref _internalSyncObject, new object(), null);
            }
            return _internalSyncObject;
        }
    }
    /// <summary>
    /// Returns the cached ResourceManager instance used by this class.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
    internal static ResourceManager ResourceManager {
        get {
            if (object.ReferenceEquals(_resourceManager, null)) {
                Monitor.Enter(InternalSyncObject);
                try {
                    if (object.ReferenceEquals(_resourceManager, null)) {
                        Interlocked.Exchange(ref _resourceManager,
                            new ResourceManager("MyApp.Resource",
                    typeof(Resource).Assembly));
                    }
                }
                finally {
                    Monitor.Exit(InternalSyncObject);
                }
            }
            return _resourceManager;
        }
    }
    /// <summary>
    /// Overrides the current thread's CurrentUICulture property for all
    /// resource lookups using this strongly typed resource class.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
    internal static CultureInfo Culture {
        get {
            return _resourceCulture;
        }
        set {
            _resourceCulture = value;
        }
    }
    /// <summary>
    /// Looks up a localized string similar to 'Hello, {0}!'.
    /// </summary>
    internal static string Hello {
        get {
            return ResourceManager.GetString(ResourceNames.Hello, _resourceCulture);
        }
    }
    /// <summary>
    /// Looks up a localized string similar to 'Message text'.
    /// </summary>
    internal static string Message {
        get {
            return ResourceManager.GetString(ResourceNames.Message, _resourceCulture);
        }
    }
    /// <summary>
    /// Formats a localized string similar to 'Hello, {0}!'.
    /// </summary>
    /// <param name="arg0">An object (0) to format.</param>
    /// <returns>A copy of format string in which the format
    /// items have been replaced by the String equivalent of
    /// the corresponding instances of Object in arguments.</returns>
    internal static string HelloFormat(object arg0) {
        return string.Format(_resourceCulture, Hello, arg0);
    }
    /// <summary>
    /// Lists all the resource names as constant string fields.
    /// </summary>
    internal class ResourceNames {
        /// <summary>
        /// Stores the resource name 'Hello'.
        /// </summary>
        internal const string Hello = "Hello";
        /// <summary>
        /// Stores the resource name 'Message'.
        /// </summary>
        internal const string Message = "Message";
    }
}

历史

  • 2.6 - 2009 年 3 月 30 日
    • 已恢复 Silverlight 兼容性。由于在类级别添加了 ObfuscationAttribute() 属性,它以前被破坏了。感谢 Eric Smith 和 r2musings 报告此问题。
    • 已移除 J# 支持。
  • 2.5 - 2009 年 2 月 20 日
    • 已将 ObfuscationAttribute() 添加到生成的资源包装器类(感谢 Friedhelm)。
    • 已解决资源包装器类构造函数缺少 XML 文档的问题(感谢 Casey Barton)。
  • 2.4 - 2008 年 10 月 20 日
    • 添加了对 Visual Studio Express 2005 和 2008 版本 (感谢 Fabien Letort 和 Ondrej Bohaciak)。
  • 2.3 - 2008 年 10 月 7 日
    • 添加了嵌套类 ResourceNames,列出了所有资源名称作为常量。
    • 已移除无参数 {PROPERTY_NAME}Format() 方法的生成。关于它们的存在存在一些混淆,所以显然这不是一个好主意,抱歉!
    • 生成的资源包装器类已设为 partial(感谢 DameonBlack)。
    • 通过将资源包装器类的构造函数设为 public 来添加 Silverlight 兼容性(感谢 Slyi)。
    • 已修复安装程序中的语言问题(感谢 Jasoncd)。
    • 改进了资源包装器生成性能。
  • 2.2 - 2008 年 5 月 24 日
    • 已修复在某些情况下生成重复格式方法的问题(感谢 Doug Richardson)。
    • 所有类型都作为全局空间的成员进行引用(感谢 Doug Richardson)。
    • 已添加对 InternalSyncObject 属性的缺失注释(感谢 Jesse Napier)。
    • 已添加抑制代码分析警告的属性(感谢 Jesse Napier)。
    • 已执行代码重构。
  • 2.1 - 2008 年 2 月 14 日
    • 已添加 InternalResXFileCodeGeneratorEx 自定义工具(感谢 Bernd Hoffmann)。
    • 生成的资源包装器类不再是 sealed(感谢 Andrea from Italy 和 Miki from Germany)。
    • 已修复资源包装器类构造函数上的 SuppressMessage 属性的问题。
    • 在资源包装器类中不使用 CompilerGenerated 属性,以确保与 .NET Compact Framework 的兼容性(感谢 reklats)。
    • 已添加无参数格式化方法(感谢 Dave Apelt)。
    • 改进了非字符串资源获取属性的生成。
  • 2.0 - 2008 年 2 月 5 日(第二次主要发布)
    • 已修复非字符串属性注释的问题(感谢 Anthony Meehan 和 Matt Rice)。
    • 资源管理器初始化现在是线程安全的(感谢 Borys Byk)。
    • 添加了 Visual Studio .NET 2008 兼容性。
  • 1.1 - 2006 年 4 月 22 日
    • 根据 Steve Hansen 的建议,改进了格式方法生成。
  • 1.0 - 2006 年 4 月 18 日
    • 初始发布。
© . All rights reserved.