Recall the code that retrieves the resource string: MessageBox.Show(resourceManager.GetString("InsufficientFunds")); This line is fragile. It relies upon a string, "InsufficientFunds", to identify the resource string. If this string includes a typo, the code will still compile successfully, but it will throw an exception at runtime. The problem is that this string cannot be verified at compile time, so it is a fragile solution. A better solution is one that the compiler can verify. To solve this problem Visual Studio 2005 introduces Strongly-Typed Resources. A strongly-typed resource is to resources what a strongly-typed dataset is to DataSets; it is a generated class that includes the resource key names as properties. The line of code can be rewritten to use a strongly-typed resource: MessageBox.Show(Form1Resources.InsufficientFunds); Form1Resources is a strongly-typed resource class in which each resource entry is represented by a property (i.e., "InsufficientFunds", in this example). The Form1.resourceManager field is no longer needed and can be removed completely. When a resource file is created in Visual Studio 2005, a corresponding file with the extension ".Designer.cs" is also created, so Form1Resources.resx has a corresponding file called Form1Resources.Designer.cs. You can see this in Solution Explorer by expanding the resx node. As you add, edit, or delete entries in the resx file, the designer file is updated. If you double-click the file, you will see the generated class. Here is the Form1Resources class with the comments stripped out (and formatted to fit this page): [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices. CompilerGeneratedAttribute()] internal class Form1Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis. SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Form1Resources() { } [global::System.ComponentModel.EditorBrowsableAttribute( global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager( "WindowsApplication1.Form1Resources", typeof(Form1Resources).Assembly); resourceMan = temp; } return resourceMan; } } [global::System.ComponentModel.EditorBrowsableAttribute( global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } internal static string InsufficientFunds { get { System.Resources.ResourceManager rm = ResourceManager; return rm.GetString( "InsufficientFunds", resourceCulture); } } internal static System.Drawing.Bitmap NationalFlag { get { System.Resources.ResourceManager rm = ResourceManager; return ((System.Drawing.Bitmap) (rm.GetObject("NationalFlag", resourceCulture))); } } } The class encapsulates its own ResourceManager object in a private static field called resourceMan. resourceMan is wrapped in a static ResourceManager property, which initializes resourceMan to this: new System.Resources.ResourceManager( "WindowsApplication1.Form1Resources", typeof(Form1Resources).Assembly); Not surprisingly, this is very similar to the line that we wrote earlier to initialize our resourceManager private field. For each entry in the resx file, a static property is created to return the resource's value. You can see in the InsufficientFunds property that it calls ResourceManager.GetString and passes the "InsufficientFunds" key. The resourceCulture private static field is initially null (therefore, ResourceManager uses Thread.CurrentThread.CurrentUICulture). You can set its equivalent static property, Culture, to specify that it should retrieve resources for a different culture. Although this is rare, you might, for example, want to display more than one culture at the same time. In the "Localizable Strings" section of this chapter, I mentioned that the .NET Framework 2.0 help includes a set of resource key naming guidelines. If you follow these guidelines, you will encounter an apparent mismatch between this advice and the designer's warnings. One of the guidelines recommends that resource keys with a recognizable hierarchy should use names that represent that hierarchy in which the different elements of the hierarchy are separated by periods. For example, menu item resource keys might be named Menu.File.New and Menu.File.Open. This is good advice, but Visual Studio 2005 reports the warning "The resource name 'Menu.File.New' is not a valid identifier". This is a consequence of the strongly-typed resource class that is generated from the resource. It is not possible to have a property called "Menu.File.New" because the period is an invalid character for an identifier. Instead, the periods are replaced with underscores, and the property is called "Menu_File_New". Despite this, I recommend that you continue to follow the resource key naming guidelines and ignore the warnings that result from the use of the period in resource key names. If you prefer not to use strongly-typed resources but are concerned that the strings passed to ResourceManager.GetString might or might not be valid, look at the "Resource string missing from fallback assembly" rule in Chapter 13. ResGenVisual Studio 2005's solution of automatically maintaining strongly-typed resources is very convenient and will be sufficient for many developers. However, if it does-n't meet your requirements because, say, you generate or maintain your own resources using a utility outside Visual Studio 2005, you need the resgen.exe command-line utility. You've seen that resgen.exe can generate binary resource files from resx XML resource files. It can also generate strongly-typed resources using the /str switch. The following command line uses the /str:C# switch to indicate that the generated file should be written in C#: resgen Form1Resources.resx /str:C# The output is: Read in 2 resources from "Form1Resources.resx" Writing resource file... Done. Creating strongly typed resource class "Form1Resources"... Done. This example creates Form1Resources.cs, which isn't the same as the Visual Studiogenerated file. The syntax of the str switch is: /str:<language>[,<namespace>[,<class name>[,<file name>]]]] To get the same output with the same filename as Visual Studio, use the following command line: resgen Form1Resources.resx /str:C#, WindowsApplication1,Form1Resources,Form1Resources.Designer.cs You can ignore the "RG0000" warning that resgen emits; the resgen-generated code is identical to the code generated by Visual Studio 2005. Another resgen command-line parameter of interest is publicClass. This parameter causes the generated class to be public instead of internal so that it can be accessed by a different assembly. StronglyTypedResourceBuilderBoth Visual Studio 2005 and resgen.exe use the System.Resources.Tools. StronglyTypedResourceBuilder class to generate strongly-typed resources. This documented .NET Framework 2.0 class is at your disposal in case you need to generate strongly-typed resources when the two existing utilities don't meet your requirements. Two such possibilities are encountered in Chapter 12 and are solved using StronglyTypedResourceBuilder:
The StronglyTypedResourceBuilder.Create method has four overloads, two of which accept a resx filename and two of which accept an IDictionary of resources to generate code for. The strategy for using a StronglyTypedResourceBuilder directly is to load your resources into an object that supports the IDictionary interface and pass this to the StronglyTypedResourceBuilder.Create method. The Create method returns a CodeDomCompileUnit object, which is the complete Code-Dom graph for the generated code. You would pass this to the CodeDomProvider. GenerateCodeFromCompileUnit method to generate the equivalent code and write it to a StreamWriter. Chapter 12 has a complete example.
|