Friday, July 9, 2010

C# - The dangers of int.Parse() on non-english systems

I ran into this issue in a similar for way back in January here, but I stumbled upon in again today and I figured another post wouldn't be a bad idea.

This simple function

double ParseString(string s)
{
     return double.Parse(s);
}
 
is a timebomb waiting for one of your fans or customers to run into if s comes from a a source that isn't translated into the specific language. For my C/C++ fans, int.Parse() is similar to atoi() with a twist we're about to get to.

My part of the world uses a period to separate the whole from the fractional part of a number, "10.0", other parts of the world uses a comma, "10,0". C# will helpfully use the latter on systems who's language is set to be one that uses this system. This can wreck you, if for example, the string comes from xml data files that you don't want to change for each language.

There are two solutions. The simplest is to rewrite the function as follows:


double ParseString(string s)
{
     return double.Parse(s, CultureInfo.InvariantCulture);
}

That will use the "standard" english system of period for decimal seperate.

The second is one that works if you are about to do some operation that is going to call Parse a large number of times (e.g. Save/Load).


double ParseString(string s)
{
     // Save off previous culture and switch to invariant for serialization.
     CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture;
     Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

     double result = double.Parse(s, CultureInfo.InvariantCulture);

     Thread.CurrentThread.CurrentCulture = previousCulture;

     return result;
}

Just be careful that if you expect your code inside the culture setting blocks to throw an exception that you plan to survive from, to rewrite this to use finally so that it always happens.

Now I have to go check every Parse() and fix up the ones that don't happen inside a save/load call...

2 comments:

Ed said...

Um, I don't think you need to actually set the thread culture if you're going to specify the culture explicitly in the parse call...

donblas said...

Yeah, if the article wasn't clear it is an either or: Each parse needs to be called under the thread culture being invariant or it needs to have the second parameter passed in.

There are some chunks of code where I'm going to call Parse() a bunch, such as save load and reading XML files. For those, I'll set the thread wide culture and reset it later.

For others, it is a one off, and just setting the second parameter is easier.