What are the difference and connection between IFormattable
, IFormatProvider
and ICustomFormatter
and when would they be used? A simpl
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
Custom formatting works base on the coordination between 3 components:
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.
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")