Here I’m assuming you’re already familiar with the SR type; if you’re not, then reading this post will definitively help.
In this post I’m going to detail a few curiosities I found while dissecting the SR type plus some suggested improvements.
Getting the assembly where a type is implemented
I can hear you saying: “…This is really an easy one, just do”:
YourType.GetType ().Assembly
Sounds obvious; but now, if you peek at SR’s internal constructor you’ll find that they get to the assembly by using:
YourType.GetType ().Module.Assembly
So I asked myself: “Why in the world are they getting to the assembly by using the Module property?”
First thing I thought was the ResourceManager not being clever enough to resolve resources located in different modules of a multi-module assembly, although that is not the case in the .NET fx -where all assemblies are composed of a single module- I thought they may have coded it that way just for “forward-compatibility”.
I fired up a console and using csc.exe and al.exe hacked up a simple two-module assembly having types and resources in each one of the modules. Then I used TypeOne.GetType().Assembly, where TypeOne is defined in module A and tried to access resources stored in module B; I expected this to miserably fail so I could confirm my simple theory. The results shown that ResourceManager is actually clever enough to find any resources no matter where they’re stored; it will consult the assembly’s manifest where resources entries contains info about the module file where they reside and from there it will properly get to the resource.
Code bloating
You know SR includes about 22 utility methods (counting overloads) to provide a strongly-typed way of handling resources. Basically what every GetXXX method does is to use the resource manager to get an object reference and then cast that reference to the specific type they’re handling with, something like this:
static public double GetDouble (CultureInfo ci, string resid) {
double num = 0;
SR sr = SR.GetLoader ();
if (sr != null)
object o = sr.resources.GetObject (resid, ci);
if (o != null)
num = (double) o;
return num;
}
Note the above lines in bold, they’re repeated in every GetXXX method (GetInt, GetFloat, GetChar, etc.)
There is a GetObject method that does exactly the same and could have been used by any of these methods, so why is this code repeated all along? The only reason I can think of is C# compiler’s inlining feature.
A generics-enabled SR
With the support for generics in Whidbey it will be possible to reduce the code-bloating nature of SR to this minimum expression:
static public T Get<T> (CultureInfo ci, string resid) {
T res = T.default;
object o = SR.GetLoader ()._resMgr.GetObject (resid, ci);
if (o != null)
res = (T)o;
return res;
}
static public T Get<T> (string resid) {
return SR.Get<T> (null, resid);
}
Don’t you think generics just rocks?
Download a skeleton SR type using generics from here (you’ll need whidbey to compile).
I’m currently performing a huge code & architecture review of a very big .NET project that includes several web applications and several frameworks. One of the first things I did was to estimate how costly would be to make all this stuff I18N ready. To my surprise (ok, not much of a surprise…) I found as many different ways to handle I18N as different teams the project has; moreover, most of them were really ugly ones… and some teams didn’t even thought about I18N.
Regarding resource handling, I decided to write a doc explaining the SR type that is used all along the .NET fx and also in other Microsoft products like Commerce Server, Exchange, etc, to present it to each team as “The SR pattern, a proven way to resource handling”. After that, I translated it to english and… this post was borned.
Hey look, it’s a double-check locking!
SR is an internal sealed type (I’m guessing named SR after “String Resources” although it can handle more than just strings). This type implements the double-check locking pattern, which is just a singleton pattern with thread safety in mind: it doesn’t expose any public constructors and it has a static method that always returns the very same instance of the type, plus it contains proper checks to make it thread-safe:
internal sealed class SR {
private static SR loader;
static SR () {
SR.loader = null;
}
static SR GetLoader () {
if (SR.loader == null) {
lock (typeof(SR)) {
if (SR.loader == null)
SR.loader = new SR ();
}
}
return SR.loader;
}
}
By following the advices in CBrumme’s Memory Model post you could make this double checking safer by using a memory barrier after construction and before assignment of loader, i.e.:
SR sr = new SR ();
Thread.MemoryBarrier ();
SR.loader = sr;
Ok, enough singleton stuff, let’s get back on topic now.
Where to put the neutral culture resources?
The most common approach is to include the neutral culture resources into your main assembly. Following the approach taken by the .NET framework itself, a resource manager is instantiated and cached in the SR type’s internal constructor:
internal SR () {
this.resMgr = new ResourceManager ("YourRootName", this.GetType().Assembly);
}
Note that the same assembly containing the SR type was specified as the one where the resource manager should look for resources. This is a good choice, as resources for a type are deployed along with it into the same assembly instead of going into a separate one, plus it enforces a bit the concept that a type’s resources are only meant to be used by that type and no other.
An alternative approach would be to ship the resources for the neutral culture in its own satellite assembly, i.e.: YourCompany.YourProduct.CommonStrings.dll, as some of the above mentioned Microsoft products actually do; but I prefer the .NET framework approach instead. Lastly, each additional supported culture should make it into its own satellite assembly.
No need to reinvent .resx
When doing the code review mentioned above, I stumped against some code of a guy that decided to create his very own resource format. You don’t need to do that, really; just add an Assembly Resource File (.resx) to your project and you’re done.
Sadly, VS.NET support for editing .resx is almost non-existent; it may be ok to use the XML view if you’re only dealing with strings but you’re on your own for anything else. Luckily you will find some free apps and add-ins to fill in this gap.
This is how a string resource looks like:
<data name="Attr_not_supported_in_directive">
<value>The '{0}' attribute is not supported by the '{1}' directive.</value>
</data>
The value for the name attribute of the data element represents the resource ID that we will need to provide when looking to access a resource’s value, which we provided as the content of the value element. Note that the {0} and {1} placeholders are there so we can easily format the string later using String.Format.
Naming resources
Regarding the naming of resources IDs, the framework tends to compose it using the name of the control or component to which the message belongs to, plus the description of the message itself, separating them with an underscore character, i.e.: