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!