Specify IFormatProvider

published: Tue, 15-Mar-2005   |   updated: Sun, 23-Jul-2006

This is one of the errors that FxCop spits out and it gets depressing, it happens so often. "Depressing, why?" I hear you ask. Mainly because I've never taken the time to understand what it's on about. Well, tonight I had time to work it out, not that it took a lot of working out. Just one of those things that if you took the time to understand it, it would hold no horrors.

Here's the resolution description, as provided by FxCop:

SomeClass.SomeMethod() makes a call to System.Int32.ToString that does not explicitly provide an IFormatProvider. This should be replaced with a call to System.Int32.ToString(System.IFormatProvider).

The Info description doesn't seem all that helpful either:

If an overload exists that takes an IFormatProvider argument, it should always be called in favor of an overload that does not. Some methods in the common language runtime convert a value to or from a string representation and take a string parameter that contains one or more characters, called format specifiers, which indicate how the value is to be converted. If the meaning of the format specifier varies by culture, a formatting object supplies the actual characters used in the string representation. In scenarios where sorting and comparison behavior should never change between cultures, specify CultureInfo.InvariantCulture.

However, it does indicate that this is all to do with cultures. No, not those things in Petri dishes, but human cultures. In fact the rule is part of FxCop's set of globalization rules.

You see calling SomeInt.ToString() presents us with an issue we should address: how the resulting string is going to be used. Sometimes the string is going to be persisted (say as part of an XML document). Sometimes the string is going to be viewed by a user. That user may have a different cultural bias about how the value in the string should look. Both of these two cases are very different uses of the resulting string, and both may look very different.

As an example, in the US the string "1,234" means "one thousand, two hundred and thirty four". In France and other European countries it means "one point two three four".

In other words, whenever we need to convert some kind of value to a string we should be taking into consideration the culture of the user of the string. This is done through an IFormatProvider instance. Not every class has an overload of ToString() that accepts an IFormatProvider, but if a particular class does, we should use it (and this is what FxCop is checking for us).

Now, it would be a real pain in the neck if we had to create an instance of IFormatProvider every time that we wanted to convert some value to a string. So, the Framework provides several pre-built ones for us. They're available as static properties from the CultureInfo class.

CultureInfo.InvariantCulture provides the formatting information that is culture-independent. In practice you use this one for persisting data as strings (and obviously you'd use it again to read that data). This is best for data that is persisted and maybe transmitted between computers since it doesn't care that the writer of the data may be in a different culture than the reader of the data.

CultureInfo.InstalledUICulture provides the culture installed with the operating system (the stuff you can change using the Regional Options item in the Control Panel).

CultureInfo.CurrentCulture gives you the culture of the current thread. This can be changed if needed and defaults to the installed operating system culture.

CultureInfo.CurrentUICulture gives you the culture that the resource manager is using to look up culture- specific resources. Culture-specific resources are what the user sees in the UI essentially (hence the name).

So, you should use the overload of ToString() that needs an IFormatProvider and you can use one of these pre-built ones, like this: SomeInt.ToString(CultureInfo.InvariantCulture).

If you don't use the ToString() overload with the IFormatProvider parameter, what happens? Well, if you trace through the implementation of Int32.ToString() with Reflector, you'll see that it eventually uses the same thing as CultureInfo.CurrentCulture, but it doesn't half go through some shenanigans to do so.

Sounds great, and it gets rid of the FxCop warning message. But why should you bother, when the default behavior seems to be OK? Well, there are three reasons, essentially. First, the warning message forces you to think about globalization issues. Here, at Configuresoft, that's one of my jobs: architecting the next version of ECM to be localizable, so that it is more attractive in markets where we have little penetration so far. Second, it demonstrates to the maintenance programmer that you have thought about the problem and have decided on a particular culture in converting the strings. Third, it means that you, or the maintenance programmer, don't have to go spelunking through the .NET Framework with Reflector (like I just did) to understand what the default behavior is.

Update

Wayne Allen brings up an important point that somehow I'd glossed over in the original article. His point is this: you should always parse a string to a value (using Parse()) using the same culture information as you used to convert it to a string in the first place. An example using DateTime (it's easier to visualize): if you convert the date 1-Mar-2005 to a string using the US culture, you'll get 03/01/2005. Parsing that back to a date using the UK culture information, you get 3-Jan-2005. Not the right date at all!