Not quite enumerating (iterating) enums
Warning/Disclaimer: This post is very likely useless and random, providing neither prescriptive guidance nor valuable suggestion. These are the ramblings of an idiot with possibly low blood sugar. There are at LEAST a dozen ways to write this silly thing. This is a largely academic post, as the end result doesn't affect the product fundamentally and the code in question only runs at startup so questions of perf is meaningless. End of speech.
Given:
I've got this giant XSD (XML Schema) with a pile of enumerations that represent languages. No, I don't own the XSD, it's a small part of a giant specification. I don't want to modify the schema.
I generated a giant C# from file from this giant schema via XSD.exe. I don't want to modify the generated code. (Random aside, when I generate code, I like to name the files *.g.cs to make it clear.)
The enum looks like this (there's 454 of them, FYI)...
public enum LanguageEnum {
AAR,
ABK,
ACE,
ACH,
//snip....
ZUL,
ZUN,
}
These are the ISO 639.1 3-letter Language Codes. These are going to be in an XML Document that will be HTTP POST'ed to a URI endpoint. I want to 'convert' them to a System.Globalization.CultureInfo via a mapping mechanism and set the CultureInfo on the current thread.
The names of the enums are important, not any implied underlying value. (They don't map to ints, etc)
The constructor for System.Globalization.CultureInfo takes a string like en-us (the ISO 639-1 Alpha 2, not these Alpha 3 codes. Restated, they want "en" or "en-us" not "ENG." However, this info IS inside of CultureInfo.ThreeLetterISOLanguageName.
I'd like clean mapping, but don't feel like writing it manually.
I wrote it this way first in a fit about 90 seconds long. Note the weird try catch. Note also that this works (i.e. achieves the goal).
(Aside,I write a lot of methods like this that lazy-initialize against a hashtable of schmutz, so this is a common pattern for me.)
private static volatile Hashtable iso639toCultureInfo = null;
private static object hashtableLock = new object();
public void SetThreadCulture(LanguageEnum foolanguage)
{
//Get a hashtable that maps the ISO639 three letter name to Windows Cultures
if(iso639toCultureInfo == null)
{
lock(hashtableLock)
{
if (iso639toCultureInfo == null)
{
iso639toCultureInfo = new Hashtable();
foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures))
{
string potentialFooLanguage = ci.ThreeLetterISOLanguageName;
try
{
//May throw an exception because FOO doesn't support this language
LanguageEnum lang = (LanguageEnum)Enum.Parse(typeof(LanguageEnum),potentialFooLanguage,true);
//May ALSO throw an exception because we already added it
iso639toCultureInfo.Add(lang.ToString(),ci);
}
catch (Exception)
{
;
}
}
}
}
}
CultureInfo fooCulture = iso639toCultureInfo[foolanguage.ToString()] as CultureInfo;
if(fooCulture != null)
{
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Threading.Thread.CurrentThread.CurrentUICulture =
fooCulture;
}
}
All the Unit Tests passed, blah blah, then I had a change of heart and did this about 30 seconds later:
private static volatile Hashtable iso639toCultureInfo = null;
private static object hashtableLock = new object();
public void SetThreadCulture(LanguageEnum foolanguage)
{
//Get a hashtable that maps the ISO639 three letter name to Windows Cultures
if(iso639toCultureInfo == null)
{
lock(hashtableLock)
{
if (iso639toCultureInfo == null)
{
iso639toCultureInfo = new Hashtable();
StringCollection languages = new StringCollection();
languages.AddRange(Enum.GetNames(typeof(LanguageEnum)));
foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures))
{
string potentialFooLanguage = ci.ThreeLetterISOLanguageName.ToUpper();
if(languages.Contains(potentialFooLanguage))
{
iso639toCultureInfo.Add(potentialFooLanguage,ci);
}
}
}
}
}
CultureInfo fooCulture = iso639toCultureInfo[foolanguage.ToString()] as CultureInfo;
if(fooCulture != null)
{
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Threading.Thread.CurrentThread.CurrentUICulture =
fooCulture;
}
}
Which way do you prefer? What would you have done?
Incidentally, of these 434 (largely obscure) languages in the enum, .NET/Windows 'supports' 50 of them as neutral cultures. Also, the enum has "FRE" for France, while the code in CultureInfo is "FRA" so I ended up changing the enum anyway. Sigh.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpatterns/html/ImpSingletonInCsharp.asp
Also, this guy:
http://www.yoda.arachsys.com/csharp/singleton.html
argues for eschewing double-checked locking in favor of static initialization (since it is lazy anyway because the class is not initialized until the first call). If you have other unrelated static methods on the outer class, and don't want calls to those methods to initialize the lazy field prematurely, then he suggests simply sticking the lazy field into an inner class. On top of that, he has "yoda" in his domain name. Let's respect the authority in that.
http://en.wikipedia.org/wiki/Double-checked_locking
Here's another thread about the pitfalls of double-checked locks:
http://blogs.msdn.com/brada/archive/2004/05/12/130935.aspx
Memory models and memory barriers can cause much pain in the brain.
Comments are closed.