38

What are the difference and connection between IFormattable, IFormatProvider and ICustomFormatter and when would they be used? A simple implementation example would be very nice too.

And I don't really mean when it is used in the .net framework, but when I would implement these myself and in that case what classes would typically implement what interface and how to do it properly.

Svish
  • 152,914
  • 173
  • 462
  • 620

3 Answers3

41
  • IFormattable is an object which supports formats in string.Format, i.e. the xxx in {0:xxx}. string.Format will delegate to an object's IFormattable.ToString method if the object supports the interface.

  • IFormatProvider is a source of configuration information that formatters use for things like culture-specific date and currency layout.

  • However, for situations like e.g. DateTime, where the instance you want to format already implements IFormattable yet you don't control the implementation (DateTime is supplied in the BCL, you can't replace it easily), there is a mechanism to prevent string.Format from simply using IFormattable.ToString. Instead, you implement IFormatProvider, and when asked for an ICustomFormatter implementation, return one. string.Format checks the provider for an ICustomFormatter before it delegates to the object's IFormattable.Format, which would in turn likely ask the IFormatProvider for culture-specific data like CultureInfo.

Here is a program which shows what string.Format asks the IFormatProvider for, and how the flow of control goes:

using System;
using System.Globalization;

class MyCustomObject : IFormattable
{
    public string ToString(string format, IFormatProvider provider)
    {
        Console.WriteLine("ToString(\"{0}\", provider) called", format);
        return "arbitrary value";
    }
}

class MyFormatProvider : IFormatProvider
{
    public object GetFormat(Type formatType)
    {
        Console.WriteLine("Asked for {0}", formatType);
        return CultureInfo.CurrentCulture.GetFormat(formatType);
    }
}

class App
{
    static void Main()
    {
        Console.WriteLine(
            string.Format(new MyFormatProvider(), "{0:foobar}", 
                new MyCustomObject()));
    }
}

It prints this:

Asked for System.ICustomFormatter
ToString("foobar", provider) called
arbitrary value

If the format provider is changed to return a custom formatter, it takes over:

class MyFormatProvider : IFormatProvider
{
    public object GetFormat(Type formatType)
    {
        Console.WriteLine("Asked for {0}", formatType);
        if (formatType == typeof(ICustomFormatter))
            return new MyCustomFormatter();
        return CultureInfo.CurrentCulture.GetFormat(formatType);
    }
}

class MyCustomFormatter : ICustomFormatter
{
    public string Format(string format, object arg, IFormatProvider provider)
    {
        return string.Format("(format was \"{0}\")", format);
    }
}

When run:

Asked for System.ICustomFormatter
(format was "foobar")
Barry Kelly
  • 41,404
  • 5
  • 117
  • 189
  • Why do you return `CultureInfo.CurrentCulture.GetFormat` in `MyFormatProvider.GetFormat`? And what would you use the `IFormatProvider` for in `MyCustomFormatter.Format`? – Svish Nov 09 '09 at 10:02
  • The default format provider is CultureInfo.CurrentCulture; it's the one that's used if you don't specify one in one of the overloads to `string.Format`. I already explained what you'd use IFormatProvider for - to provide configuration info (e.g. ask for CultureInfo), for things like date formatting (e.g. to use CultureInfo.DateTimeFormat). – Barry Kelly Nov 09 '09 at 11:33
  • Don't use `return CultureInfo.CurrentCulture.GetFormat(formatType)` in the GetFormat it will break. Eventually it will be cast into an ICustomFormatter. see: https://referencesource.microsoft.com/#mscorlib/system/text/stringbuilder.cs,2c3b4c2e7c43f5a4 – Wouter Jun 12 '20 at 23:49
  • @Wouter every time you use `CultureInfo.CurrentCulture` as `IFormatProvider` it will have `GetFormat` called on it. If that breaks, it's a problem with that instance of `CultureInfo`, since it implements `IFormatProvider`. Is there a specific implementation that you found a problem with? – Barry Kelly Jun 14 '20 at 02:04
  • See: https://referencesource.microsoft.com/#mscorlib/system/decimal.cs,0b7d635398c0e319 and https://referencesource.microsoft.com/#mscorlib/system/globalization/numberformatinfo.cs,9c4284b5db21c23a for how the code path will expect a `IFormatProvider` (usually `CultureInfo`) to support `GetFormat`, if you want to print a decimal with current culture (e.g. commas vs dots for separators vs decimal). You can't provide that `CultureInfo` without it having `GetFormat` called for `ICustomFormatter` earlier. – Barry Kelly Jun 14 '20 at 02:11
  • https://referencesource.microsoft.com/#mscorlib/system/globalization/cultureinfo.cs,1365 shows that it will either return NumberFormatInfo or DateTimeFormatInfo both do not implement ICustomFormatter. – Wouter Jun 14 '20 at 13:46
  • @BarryKelly I want to make a custom format provider to customize a custom struct but i don't want to change anything else about the culture used for example try something like: `string.Format(new MyProvider(), "{0} {1} {2}", mystruct, 0.123, true);` Your solution doesn't work because formatType will always be ICustomFormatter – Wouter Jun 14 '20 at 14:16
2

Custom formatting works base on the coordination between 3 components:

  • Formattable
  • Format provider
  • Formatter

The formattable objects are instances that can use a format provider along with a format string to format their data by implementing the IFormattable interface. Basically, they will request the format provider to get a formatter and then use the format string which are format instructions to ask the formatter to format their instances. Date/time and numeric types are examples of formattable types.

The format providers are classes that implement the IFormatProvider interface. They are responsible for returning the formatter object base on the format type requested by the caller. The format type could be the type of whatever that a format provider could understand while the returned formatter should be whatever that the caller (the formattable `object in most cases) could use to format their data.

The formatters are objects which are responsible for providing formatting services. For date/time and numeric types, format providers are also formatters which are CultureInfo, DateTimeFormatInfo, and NumberFormatInfo.

In composite formatting implemented by some methods such as String.Format, Console.WriteLine or StringBuilder.AppendFormat, when a format provider is passed to them, they always ask the format provider for a formatter that implements the ICustomFormatter interface. This allows developers to provide various custom formatting to these methods.

Muhammad Usman Bashir
  • 1,441
  • 2
  • 14
  • 43
0

IFormattable is an object that supports different (named/custom) formats - for example, numbers, etc. By using an interface, multiple blocks of code can use the value and a format string, and this is common (for example) in data-binding and string.Format.

An IFormatProvider fills in some gaps dealing with formatting - particularly i18n. Most commonly, a CultureInfo is used as the provider, either giving a specific local format, or the invariant culture.

As far as I know, ICustomFormatter is unrelated, and ties more into serialization (BinaryFormatter). I could be wrong...

An example of an IFormattable object:

IFormattable d = 123.45M;
string s1 = d.ToString("c", CultureInfo.CurrentCulture), // local currency
       s2 = d.ToString("c", CultureInfo.InvariantCulture); // invariant currency
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    This is a very old answer, but for the sake of The Children: `ICustomFormatter` is indeed about string formatting, and for adding support for format strings to types that aren't `IFormattable` implementations (or for providing different format strings to `IFormattable` objects). `IFormatter` is the one about serialization. – Matt Enright Jan 30 '14 at 21:18
  • IFormattable is also used during string interpolation. – Wouter Jun 14 '20 at 14:26