Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to implement a custom formatter but ICustomFormatter.Format is never called

I'm trying to figure out how IFormatProvider and ICustomFormatter work after following Format TimeSpan in DataGridView column on how to customize a TimeSpan in a DataGridView. I've created a completely custom formatter that always returns "foo" regardless of what it is formatting.

I'm using it on Int but I assume it should work on all types as it doesn't check the value being passed, it just returns "foo".

class MyFormatter : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        Console.WriteLine("GetFormat");
        return this;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        Console.WriteLine("Format");
        return "foo";
    }
}

And I'm passing it to int.ToString():

int number = 10;
Console.WriteLine(number.ToString(new MyFormatter()));

What I'm getting is:

GetFormat
10

While what I was hoping to get is:

GetFormat
Format
foo

Edit: I found How to create and use a custom IFormatProvider for DateTime? and the answers there say that DateTime.ToString() will not accept anything but DateTimeFormatInfo or CultureInfo and an object will be rejected if it's not of these types even if it implements ICustomFormatter - https://stackoverflow.com/a/2382481/492336.

So my question is then does that hold in all cases of the ToString() methods? Does it hold also for DataGridView, and in which cases can I pass a truly custom formatter?

like image 685
sashoalm Avatar asked Nov 26 '22 03:11

sashoalm


1 Answers

When you call ToString on integer and provide IFormatProvider, it will try to extract NumberFormatInfo from it, roughly in the following way:

public static NumberFormatInfo GetInstance(IFormatProvider formatProvider)
{
  CultureInfo cultureInfo = formatProvider as CultureInfo;
  if (cultureInfo != null && !cultureInfo.m_isInherited)
    return cultureInfo.numInfo ?? cultureInfo.NumberFormat;
  NumberFormatInfo numberFormatInfo = formatProvider as NumberFormatInfo;
  if (numberFormatInfo != null)
    return numberFormatInfo;
  if (formatProvider != null)
  {
    NumberFormatInfo format = formatProvider.GetFormat(typeof (NumberFormatInfo)) as NumberFormatInfo;
    if (format != null)
      return format;
  }
  return NumberFormatInfo.CurrentInfo;
}

So you see if all else fails, it calls GetFormat with type equal to NumberFormatInfo, and expects NumberFormatInfo back. You don't return it from GetFormat, so it uses default formatter instead (current cutlure). So valid way to use it in this case would be something like:

class MyFormatter : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(NumberFormatInfo)) {
            return new NumberFormatInfo()
            {
                // something here
            };
        }
        Console.WriteLine("GetFormat");
        return this;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        Console.WriteLine("Format");
        return "foo";
    }
}

But this way I doubt you can return arbitrary values like "foo" for any number.

like image 88
Evk Avatar answered Apr 07 '23 20:04

Evk