C#:IFormattable、IFormatProvider 和 ICustomFormatter 之间的连接,以及何时使用什么

分享于2023年04月22日 c# formatting 问答
【问题标题】:C#: Connection between IFormattable, IFormatProvider and ICustomFormatter, and when to use whatC#:IFormattable、IFormatProvider 和 ICustomFormatter 之间的连接,以及何时使用什么
【发布时间】:2023-04-21 21:57:01
【问题描述】:

IFormattable IFormatProvider ICustomFormatter 有什么区别和联系,什么时候用?一个简单的实现示例也会非常好。

我并不是说什么时候在 .net 框架中使用它,而是什么时候我会自己实现这些,在这种情况下,哪些类通常会实现什么接口以及如何正确地实现它。


【解决方案1】:
  • IFormattable 是一个支持 string.Format 格式的对象,即 {0:xxx} 中的 xxx 。如果对象支持接口, string.Format 将委托给对象的 IFormattable.ToString 方法。

  • IFormatProvider 是格式化程序用于特定文化的日期和货币布局等配置信息的来源。

  • 但是,对于诸如 DateTime ,您要格式化的实例已经实现 IFormattable 但您无法控制实现( DateTime 在 BCL 中提供,您不能轻易替换它),有一种机制可以防止 @ 987654334@ 来自简单地使用 IFormattable.ToString 。相反,您实现 IFormatProvider ,当被要求提供 ICustomFormatter 实现时,返回一个。 string.Format 在委托给对象的 IFormattable.Format 之前检查提供程序的 ICustomFormatter ,这反过来可能会向 IFormatProvider 询问特定于文化的数据,例如 CultureInfo

这是一个程序,它显示了 string.Format IFormatProvider 请求什么,以及控制流程是如何进行的:

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

它打印这个:

Asked for System.ICustomFormatter
ToString("foobar", provider) called
arbitrary value

如果格式提供程序更改为返回自定义格式化程序,它将接管:

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

运行时:

Asked for System.ICustomFormatter
(format was "foobar")

【讨论】:

  • 为什么在 MyFormatProvider.GetFormat 中返回 CultureInfo.CurrentCulture.GetFormat ?你会在 MyCustomFormatter.Format 中使用 IFormatProvider 做什么?
  • 默认格式提供者是 CultureInfo.CurrentCulture;如果您没有在 string.Format 的重载之一中指定一个,则使用它。我已经解释了您将使用 IFormatProvider 的用途 - 提供配置信息(例如,询问 CultureInfo)、日期格式(例如,使用 CultureInfo.DateTimeFormat)。
  • 不要在 GetFormat 中使用 return CultureInfo.CurrentCulture.GetFormat(formatType) ,它会中断。最终它将被转换为 ICustomFormatter。见: referencesource.microsoft.com/#mscorlib/system/text/…
  • @Wouter 每次你使用 CultureInfo.CurrentCulture 作为 IFormatProvider 它都会调用 GetFormat 。如果出现问题,则说明 CultureInfo 的实例存在问题,因为它实现了 IFormatProvider 。您是否发现某个特定的实现存在问题?
  • 查看: referencesource.microsoft.com/#mscorlib/system/… referencesource.microsoft.com/#mscorlib/system/globalization/… ,了解代码路径如何期望 IFormatProvider (通常为 CultureInfo )支持 GetFormat ,如果您想使用当前文化打印小数(例如,分隔符与小数的逗号与点)。如果没有 GetFormat 之前调用 ICustomFormatter ,您将无法提供 CultureInfo