I once worked on a Web application where, several months into development, the business forces driving the project discovered that the product name was already trademarked by another company. After the discovery, it was a major undertaking to find and change all code references to the former product name (and all variations of it, including graphic images and logos). This taught me a lesson and brings us to the focus of this article: Constants can make your life much easier.
I cringe every time I see a quoted string in application code or numbers other than 0 or 1. For those who are unfamiliar with the terms, a magic number is any numeric literal used in your program other than the numbers 0 and 1, and a string literal is any quoted string. Good programming practices state that instead of literals, you should always define a constant for the literal with a semantic name and use the semantic name in your program code. This makes the code easier to read. And if the constant ever needs to be changed, it can be changed in one place, which will affect all uses of it. For example, consider the following two lines of code:
bool seatingAvailable = (guests < 150);
bool seatingAvailable = (guests < MaximumOccupancy);
The first line leaves the reader to guess what semantic meaning the literal value 150 has, while the second makes this clearly obvious. And if the value of MaximumOccupancy is changed, the change will appropriately affect all calculations based on it.
Being resourceful
Fortunately, you can easily create and store all the constants you need using resource files in .NET. Any content imaginable can be placed there using the *.resx file format. One of the best resource editors I’ve found is Lutz Roeder’s Resourcer, and Visual Studio .NET includes built-in support for editing string resources in *.resx files.
Using resources in your own programs is easy. You must build an internally accessible class using the singleton design pattern to access all of your assembly’s resources. Then, you can add resources as you develop the application and have them all in one place if you need to change them. Listing A shows an outline for such a class.
The class design in Listing A implements the singleton design pattern, in that only one instance of the class will ever exist throughout the life of the program. (It holds a reference to itself in the instance static field.) The instance of this class also holds a reference to a ResourceManager object, through which we will access our assembly’s resources.
In the class constructor (private AsmRes()), we load the *.resx resources, which are saved with the same name as this class and exist in this assembly. (If you name your *.resx file differently, you will need to adjust these parameters.)
The GetSingleton method will be called by the static methods we add to this class. The job of GetSingleton is to retrieve a reference to the singleton instance stored in the instance field.
Now we’re ready to begin adding the functionality necessary to retrieve string and numeric resources from the *.resx file. Listing B contains four static methods that are used to retrieve string resources. The method signatures that include CultureInfo can be used to internationalize your application in the future. Note that in our resource file, we can use formatting strings such as “Hello, {0}” and retrieve the correct string using one of the methods having a variable argument list (params).
As an example of how to use the GetString methods, we might add a constant to our AsmRes class and then use it from client code, as shown in the snippet in Listing C.
For numeric and other types of localizable resources that should not be hard-coded into the program, we can define similar methods. Listing D contains two static methods that illustrate how to retrieve ints from our *.resx file.
Additional methods
You can add more methods to handle each of the data types stored in your *.resx file. Note that to avoid the boxing and unboxing operations on value types such as int, these can be defined as int constants in this class for better performance, as long as the values won’t change during localization.