Scott Hanselman

Not quite enumerating (iterating) enums

March 19, '06 Comments [9] Posted in ASP.NET | Internationalization | XML
Sponsored By

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.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Sunday, March 19, 2006 6:39:59 PM UTC
I would go with the second (non-exception) version any day of the week. It may take an extra 5 seconds to understand, but it's a lot cleaner. Also it's always a good general practice to avoid catching exceptions needlessly, because although this is not a performance-sensitive area the act of conciously avoiding exceptions instills the same instinct in all other cases (at least it'll make you stop and consider whether or not that is the correct approach).
Sunday, March 19, 2006 10:05:01 PM UTC
I agree, the second version is much cleaner. If you are writing C# 2.0, consider using ToUpperInvariant() instead of ToUpper(). That way, you add an IFormatProvider to the new string (ToUpper() creates a new string).
Monday, March 20, 2006 12:42:33 AM UTC
I also prefer the second version for the reasons mentioned above, plus I generally try to avoid the *.Parse() functions if at all possible. I think the second version was actually easier to read, too.
Monday, March 20, 2006 10:15:26 AM UTC
Just as an aside, since you are using double-checked locking, you should declare iso639toCultureInfo volatile:

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.
Monday, March 20, 2006 11:05:46 AM UTC
I must have been very tired not to have noticed that -- double-checked locking is as useful as it is dangerous. The wikipedia entry on it is a good place to start. As a general principle, unless you know _EXACTLY_ what you're doing in a very performance-sensitive scenario (this isn't one), double-checked locking is a very bad idea.

http://en.wikipedia.org/wiki/Double-checked_locking
Monday, March 20, 2006 1:39:13 PM UTC
Why the need to constantly state how long it took you to do something? Just wondering..
Me
Monday, March 20, 2006 4:26:08 PM UTC
To Me (anonymous): huh? I was pointing out that it didn't take long because I wasn't thinking deeply about it. The one that didn't take long wasn't very good, as the commenters seem to agree. Why the need to stay anonymous? ;)
Scott Hanselman
Monday, March 20, 2006 4:28:48 PM UTC
Gulli...interesting stuff...I am not a fan of static initialization (got turned off in C++ years) but those are compelling links...the volitile keyword makes total sense though...I'll need to run some tests. Thanks!
Scott Hanselman
Wednesday, March 22, 2006 8:19:49 AM UTC
Scott -

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.
mikeb
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.