I am reading C#6.0 in a nutshell, in the book it says user can also write their own format provider that works in conjunction with existing types. And on page 245 there is a sample code as below:
I don't know if the check could be omitted when implement GetFormat function? Can I just use
return this
instead of
if (formatType == typeof(ICustomFormatter))
return this;
return null;
in that implementation? As I think formatType will always be typeof ICustomFormatter as per class declaration, because it implemented the ICustomFormatter interface?
using System;
using System.Globalization;
using System.Text;
namespace Page245
{
class Program
{
static void Main(string[] args)
{
double n = -123.45;
IFormatProvider fp = new WordyFormatProvider();
Console.WriteLine(string.Format(fp, "{0:C} in words is {0:W}", n));
}
public class WordyFormatProvider : IFormatProvider, ICustomFormatter
{
static readonly string[] _numberWords =
"zero one two three four five six seven eight nine minus point".Split();
IFormatProvider _parent; // Allows consumers to chain format providers
public WordyFormatProvider() : this(CultureInfo.CurrentCulture) { }
public WordyFormatProvider(IFormatProvider parent)
{
_parent = parent;
}
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter)) // Can this check be omitted?, just return this directly, as formatType will always be typeof ICustomFormatter as per class declaration, it implements the ICustomFormatter interface??
return this;
return null;
}
public string Format(string format, object arg, IFormatProvider prov)
{
// If it's not our format string, defer to the parent provider:
if (arg == null || format != "W")
return string.Format(_parent, "{0:" + format + "}", arg);
StringBuilder result = new StringBuilder();
string digitList = string.Format(CultureInfo.InvariantCulture, "{0}", arg);
foreach (char digit in digitList)
{
int i = "0123456789-.".IndexOf(digit);
if (i == -1) continue;
if (result.Length > 0) result.Append(' ');
result.Append(_numberWords[i]);
}
return result.ToString();
}
}
}
}
I have changed code and the results doesn't seem to change, but I would like to ask if there is any potential downside if I modify the code like mentioned? Thanks for any comments or answer.
It writes to console as below and result doesn't change after modification -€123.45 in words is minus one two three point four five
There are times when IFormatProvider.GetFormat
is called with formatType
set to something other than your formatter type.
Let's use a really simple test IFormatProvider
:
public class CustomFormatter : IFormatProvider
{
public object GetFormat(Type formatType)
{
Console.WriteLine("Called with " + formatType);
return null;
}
}
Now, let's try some things:
string s = 3.ToString(new CustomFormatter());
Here, GetFormat
is called with typeof(NumberFormatInfo)
.
string s = DateTime.Now.ToString(new CustomFormatter());
Here, GetFormat
is called with typeof(DateTimeFormatInfo)
.
string s = string.Format(new CustomFormatter(), "{0}", 3);
Here, GetFormat
is first called with typeof(ICustomFormatter)
, and then with typeof(NumberFormatInfo)
.
Now it's true that in the current implementation of .NET, if you pass your own IFormatProvider
to string.Format
, it will first ask it for an ICustomFormatter
, and if that returns null
, for an NumberFormatInfo
/ DateTimeFormatInfo
(if appropriate).
However, you cannot rely on this. Someone might use your IFormatProvider
somewhere else (e.g. passing to an object which implements IFormattable
), in which case your IFormatProvider
might be asked for a different formatType
. It might be that the implementation of string.Format
changes in the future, and it asks for a NumberFormatInfo
before or as well as an ICustomFormatter
.
The docs for IFormatProvider.GetFormat
say:
Returns
An instance of the object specified by
formatType
, if theIFormatProvider
implementation can supply that type of object; otherwise,null
.
For everything to work, now and in the future, you need to follow this. If your IFormatProvider
can supply an instance of the requested type, it should do so. Otherwise, it should return null
.
As for why string.Format
first calls our CustomFormatter
with typeof(ICustomFormatter)
, and then with typeof(NumberFormatInfo)
, see this doc:
How arguments are formatted
Format items are processed sequentially from the beginning of the string. Each format item has an index that corresponds to an object in the method's argument list. The Format method retrieves the argument and derives its string representation as follows:
If the argument is
null
, the method insertsString.Empty
into the result string. You don't have to be concerned with handling aNullReferenceException
fornull
arguments.If you call the
Format(IFormatProvider, String, Object[])
overload and the provider object'sIFormatProvider.GetFormat
implementation returns a non-nullICustomFormatter
implementation, the argument is passed to itsICustomFormatter.Format(String, Object, IFormatProvider)
method. If the format item includes aformatString
argument, it is passed as the first argument to the method. If theICustomFormatter
implementation is available and produces a non-null string, that string is returned as the string representation of the argument; otherwise, the next step executes.If the argument implements the
IFormattable
interface, itsIFormattable.ToString
implementation is called.The argument's parameterless
ToString
method, which either overrides or inherits from a base class implementation, is called.
The argument is not null
, so we skip the first bullet.
We called the overload of string.Format
which takes an IFormatProvider
, but our IFormatProvider.GetFormat
implementation returned null
, so we skip over the second bullet.
Our argument, an Int32
, does implement IFormattable
, so its IFormattable.ToString
implementation is called (and our IFormatProvider
is passed in). Int32.ToString(IFormatProvider provider)
calls IFormatProvider.GetFormat
and passes in typeof(NumberFormatInfo)
.