Is there a good reference implementation for IFormattable? I plan to have at least one custom IFormatProvider
for my object, and I want to make sure that the wiring is correct for the different possible parameter sets passed to IFormattable.ToString(string, IFormatProvider)
.
What I have so far:
public class MyDataClass : IFormattable
{
/// <seealso cref="IFormattable.ToString(string, IFormatProvider)"/>
public string ToString(string format, IFormatProvider formatProvider)
{
ICustomFormatter formatter = (ICustomFormatter)formatProvider.GetFormat(typeof(ICustomFormatter));
return formatter.Format(format, this, formatProvider);
}
}
But it seems like there are other potential situations that should be covered, i.e.:
- If
formatProvider
is null, should I fall back to this.ToString()
?
- If
formatProvider.GetFormat(typeof(ICustomFormatter))
returns null
, is there a particular exception I should throw?
Any blog posts / code samples / MSDN references are appreciated.
You seem to misunderstand the design of the .NET Framework's formatting infrastructure. ICustomFormatter
should never be referenced inside an implementation of IFormattable.ToString
, since that clashes with the intended purpose of that interface.
IFormattable
An object should only implement IFormattable
if it knows how to format itself (ideally it should delegate that to another class of course, but there would be deliberate coupling here). An object may know how to format itself multiple different ways, so the format string allows you to pick between them. Even with that there may still be missing information, such things that vary by culture. Therefore there is a second parameter that provides such information indirectly.
The type passed to IFormatProvider.GetFormat
is intended to be a type or interface specific to the class the IFormatProvider
was provided to.
For example, the built-in numeric types want to be able to retrieve an instance of System.Globalization.NumberFormatInfo
, while the DateTime
related classes want to be able to retrieve a System.Globalization.DateTimeFormatInfo
.
Implementing IFormattable
So let's imagine we are creating some new self-formatting class. If it knows only one way to format itself, it should simply override object.ToString()
, and nothing more. If the class knows more than one way to format itself should implement IFormattable
.
The format
parameter
Per the documentation of IFormattable.ToString
the format string of "G"
(which represents the general format) must be supported. It is recommended that a null or empty format string be equivalent to a format string of "G"
. The exact meaning is otherwise up to us.
The formatProvider
parameter
If we need anything culture specific, or that would otherwise vary we need to utilize the IFormatProvider
parameter. There would be some type that we request from it using IFormatProvider.GetFormat
. If the IFormatProvider
is null, or if IFormatProvider.GetFormat
returns null for the type we want we should fall back to some default source for this varying information.
The default source need not be static. It is conceivable that the default source might be a user setting in the app, and the formatProvider
is used to preview option changes and/or when a fixed format is needed for serialization.
It is also possible that formatting may involve formatting some sub-object. In that case you probably want to pass the IFormatProvider
down. MSDN has an excellent example of implementing IFormattable
that shows this very case.
Other ToString
overloads
When implementing IFormattable
it is important that Object.ToString()
be overridden in a manner equivalent to the following
public override string ToString()
{
return this.ToString(null, System.Globalization.CultureInfo.CurrentCulture);
}
Doing so ensures that somestring + yourobject
is equivalent to string.Format("{0}{1}",somestring, yourobject)
, which your users will expect to be true.
For the convenience of your users, you should probably provide string ToString(string format)
. Also if your default format has any varying components that can benefit from the IFormatProvider
, you may also want to provide public string ToString(IFormatProvider provider)
.
ICustomFormatter
So what do we do if we want to format a class that does not know how to format itself, or we want to use some format not supported by the class itself. That is where ICustomFormatter becomes relevant. An IFormatProvider
that can provide the ICustomFormatter
type can be passed as the IFormatProvider
parameter in methods like string.Format
and StringBuilder.AppendFormat
.
The provided ICustomFormatter
has its Format
method called for each formatting that string.Format
does. If the ICustomFormatter
is unfamiliar with the format string used or has no support for that type it simply delegates to IFormattable.ToString
or Object.ToString
. The ICustomFormatter
documentation provides a list of what is needed if you are formatting an object that does not already provide formatting support, and what is needed if you merely want to add an extra format to an existing IFormattable
. It also provides an example of the adding an extra format case.
Reference
This MSDN page provides a great overview of the .NET formatting system, and provides links to pretty much all the other relevant pages in MSDN. It is the best place to start for almost any formatting related question.
For such questions a good source of information can be found inside Mono source code. You'll likely find quite a few uses of this inside its mscorlib.dll code.