IFormatProvider and NumberFormatInfo nightmare

I was implementing FormatProvider to support a special unit of measure called basis points (bp) which basically means 1/10000.

class MyFormatInfo: IFormatProvider, ICustomFormatter
{
   #region IFormatProvider Members
   public object GetFormat(
Type formatType)
   {
      return this;
   }
   #endregion

   #region ICustomFormatter Members
   public string Format(string format, object arg,
IFormatProvider formatProvider)
   {
      string aLowerFormat = format.ToLowerInvariant();
      switch (aLowerFormat)
      {
         case
“bp”:
            return FormatNumeric(arg.ToString(), 10000);
         case
“pc”:
            return FormatNumeric(arg.ToString(), 100);
      }
      return string.Format(
CultureInfo.CurrentCulture, format, arg);
   }
 

   private static string FormatNumeric(string theNumber, decimal theMultiplier)
   {
      decimal aNumericValue;
      if (decimal.TryParse(theNumber, out aNumericValue))
      {
         aNumericValue *= theMultiplier;
         return aNumericValue.ToString(
“N”, CultureInfo.CurrentCulture);
      }
      else

      {
         return string.Empty;
      }
   } 
   #endregion

}

This works great when used with string.Format such as string.Format(“{0:bp}”, 0.01).

However, once it is being used with .ToString(new MyFormatProvider()), nothing works. It didn’t even hit the Format() method. The ToString(IFormatProvider) method supposedly works with custom format provider. (Unless why would it even accept IFormatProvider)

You might say, “Then just use the damn string.Format method.” I know, I know, but how would you bind your object to a datagrid and have the decimal or double value be displayed in the custom format?

grid.CellStyle.FormatProvider = new MyFormatProvider();
grid.CellStyle.Format = “bp”;

A trip to Reflector reveals the ugly implementation of decimal.ToString(IFormatProvider) and double.ToString(IFormatProvider). The truth is, they will check if the IFormatProvider is actually a NumberFormatInfo. If it’s not, then it will ignore everything and use the NumberFormatInfo from the CurrentCulture.

I don’t really understand why it was implemented that way, but okay Microsoft, how about I subclass from NumberFormatInfo. Then my format provider will be NumberFormatInfo, right? Right, except for one small detail: NumberFormatInfo is a sealed class. Bang…I hit my head against the wall again.

Then, my last resort is try to find standard format hack and lucky me, I found out that the format ‘%’ is doing no more than multiply the original number with 100 just like how the ‘,’ format is dividing by 100. So, here goes it.

NumberFormatInfo aCustomFormat = (NumberFormatInfo)

CultureInfo.CurrentCulture.NumberFormat.Clone();

aCustomFormat.PercentSymbol = “”;

decimal x = 0.0125M;

Console.WriteLine(x.ToString(“0.##%\%”, aCustomFormat));    // 1.25%

Console.WriteLine(x.ToString(“0.##%%bp”, aCustomFormat));   // 125bp

I was lucky this time, but if my requirements cannot be met by tricking ‘%’ or ‘,’, then I will be in trouble. Hopefully somebody will fix the ToString(IFormatProvider) implementation in the future or explain why it should be implemented this way.

Advertisements
Explore posts in the same categories: .NET

2 Comments on “IFormatProvider and NumberFormatInfo nightmare”

  1. Mike Says:

    1. “double” has its own formatting – so it will be FAST. that is also why the info class is sealed, etc.

    2. you can modify the Grid CellFormatting event to chage the object.ToString into formatting with string.Format (which explcitly support extended formatting) to fix your problem.

  2. Jax Says:

    Ermmm if you look at reflector a bit more (i’m looking at CF but its probably the same), you’ll note that it also does this:

    numInfo = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo;
    if (numInfo != null)
    {
    return numInfo;
    }

    That means in the GetFormat method you can check to see if it is asking for a NumberFormatInfo, if it is then you can return your own instance of this class that you can create in this method as opposed to just returning “this”.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: