C#: Connection between IFormattable, IFormatProvider and ICustomFormatter, and when to use what

c#, formatting

Solution

`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")

Problem

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.

Original source