Reference Implementation for IFormattable
Asked Answered
E

2

13

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.:

  1. If formatProvider is null, should I fall back to this.ToString()?
  2. If formatProvider.GetFormat(typeof(ICustomFormatter)) returns null, is there a particular exception I should throw?

Any blog posts / code samples / MSDN references are appreciated.

Eskisehir answered 8/1, 2013 at 18:13 Comment(0)
W
45

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.

Wingspread answered 10/1, 2013 at 23:10 Comment(2)
Thanks for the detailed answer and references!Eskisehir
When I first read this question I was basically as confused about all of this as you were, so I Looked at the framework in reflector, and started reading MSDN pages, and I only got more confused. It took ontil I had read the majority of the MSDN pages before I found that main reference page. Even then, It took a while before it started to click. There are some tricky parts. For example, I suspect the only reason ICustomFormatter is used with IFormatProvider is because it was a late addition to the 1.0 framework, and they did not want to add a new string.Format overload.Wingspread
T
1

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.

Trustless answered 8/1, 2013 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.