为什么在C#中实现ICustomFormatter接口时需要进行格式类型检查?

Why need to perform a formatType check when implement a ICustomFormatter interface in C#?

我正在简要阅读 C#6.0,在书中它说用户还可以编写自己的格式提供程序,与现有类型结合使用。在第245页有一个示例代码如下:

不知道实现GetFormat函数时是否可以省略检查?我可以只使用

return this 

而不是

if (formatType == typeof(ICustomFormatter)) 
    return this;
return null; 

在那个实现中?正如我认为的,根据 class 声明 formatType 将始终是 ICustomFormatter 的类型,因为它实现了 ICustomFormatter 接口?

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();
            }
        }

    }
}

我已经更改了代码,结果似乎没有改变,但我想问一下,如果我像上面提到的那样修改代码,是否有任何潜在的缺点?感谢您的任何评论或回答。

写入控制台如下,修改后结果不变 - €123.45 是减一二三点四五

有时调用 IFormatProvider.GetFormatformatType 设置为 其他 而非格式化程序类型。

让我们使用一个非常简单的测试 IFormatProvider:

public class CustomFormatter : IFormatProvider
{
    public object GetFormat(Type formatType)
    {
        Console.WriteLine("Called with " + formatType);
        return null;
    }
}

现在,让我们尝试一些事情:

string s = 3.ToString(new CustomFormatter());

此处,GetFormattypeof(NumberFormatInfo) 一起调用。

string s = DateTime.Now.ToString(new CustomFormatter());

此处,GetFormattypeof(DateTimeFormatInfo) 一起调用。

string s = string.Format(new CustomFormatter(), "{0}", 3);

这里先用typeof(ICustomFormatter)调用GetFormat,然后用typeof(NumberFormatInfo)调用。


现在确实在.NET的当前实现中,如果你将自己的IFormatProvider传递给string.Format,它会首先要求它一个 ICustomFormatter,如果 returns null,一个 NumberFormatInfo / DateTimeFormatInfo(如果合适的话)。

但是,你不能依赖这个。有人可能会在其他地方使用您的 IFormatProvider(例如传递给实现 IFormattable 的对象),在这种情况下,您的 IFormatProvider 可能会被要求使用不同的 formatType。可能以后 string.Format 的实现会发生变化,它要求 NumberFormatInfo before 或者 以及 一个ICustomFormatter.

docs 对于 IFormatProvider.GetFormat 说:

Returns

An instance of the object specified by formatType, if the IFormatProvider implementation can supply that type of object; otherwise, null.

为了一切正常,现在和将来,您都需要遵循这一点。如果您的 IFormatProvider 可以提供所请求类型的实例,它应该这样做。否则,应该 return null.


至于为什么string.Format先用typeof(ICustomFormatter)调用我们的CustomFormatter,再用typeof(NumberFormatInfo)调用,见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 inserts String.Empty into the result string. You don't have to be concerned with handling a NullReferenceException for null arguments.

  • If you call the Format(IFormatProvider, String, Object[]) overload and the provider object's IFormatProvider.GetFormat implementation returns a non-null ICustomFormatter implementation, the argument is passed to its ICustomFormatter.Format(String, Object, IFormatProvider) method. If the format item includes a formatString argument, it is passed as the first argument to the method. If the ICustomFormatter 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, its IFormattable.ToString implementation is called.

  • The argument's parameterless ToString method, which either overrides or inherits from a base class implementation, is called.

参数不是 null,所以我们跳过第一个项目符号。

我们调用了 string.Format 的重载,它接受了 IFormatProvider,但是我们的 IFormatProvider.GetFormat 实现 returned null,所以我们跳过第二个子弹.

我们的论点,Int32确实实现了IFormattable,所以它的IFormattable.ToString实现被调用了(而我们的IFormatProvider 被传入)。 Int32.ToString(IFormatProvider provider) 调用 IFormatProvider.GetFormat 并传入 typeof(NumberFormatInfo).