如何:定义和使用自定义数值格式提供程序

.NET 使你可以全面控制数值的字符串表示形式。 它支持用于自定义数值格式的以下功能:

  • 标准数字格式字符串,提供一组预定义格式以用于将数字转换为其字符串表示形式。 可以将它们与包含 format 参数的任何数字格式设置方法(如 Decimal.ToString(String))结合使用。 有关详细信息,请参阅标准数字格式字符串

  • 自定义数字格式字符串,提供一组可以进行组合以定义自定义数字格式说明符的符号。 它们还可以与包含 format 参数的任何数字格式设置方法(如 Decimal.ToString(String))结合使用。 有关详细信息,请参阅自定义数字格式字符串

  • 自定义 CultureInfoNumberFormatInfo 对象,定义用于显示数值的字符串表示形式的符号和格式模式。 可以将它们与包含 provider 参数的任何数字格式设置方法(如 ToString)结合使用。 provider 参数通常用于指定区域性专用格式设置。

在某些情况下(例如当应用程序必须显示格式化帐号、标识号或邮政编码),这三种方法都不合适。 借助 .NET,你还可以定义既不是 CultureInfo 也不是 NumberFormatInfo 对象的格式设置对象,用于确定如何设置数值的格式。 本主题提供用于实现这类对象的分步说明,并提供对电话号码设置格式的示例。

定义自定义格式提供程序

  1. 定义实现 IFormatProviderICustomFormatter 接口的类。

  2. 实现 IFormatProvider.GetFormat 方法。 GetFormat 是格式设置方法(如 String.Format(IFormatProvider, String, Object[]) 方法)调用的回调方法,用于检索实际负责执行自定义格式设置的对象。 GetFormat 的典型实现执行以下操作:

    1. 确定以方法参数形式传递的 Type 对象是否表示 ICustomFormatter 接口。

    2. 如果此参数确实表示 ICustomFormatter 接口,GetFormat 会返回对象,用于实现负责执行自定义格式设置的 ICustomFormatter 接口。 通常,自定义格式设置对象返回其自身。

    3. 如果参数不表示 ICustomFormatter 接口,GetFormat 返回的是 null

  3. 实现 Format 方法。 此方法由 String.Format(IFormatProvider, String, Object[]) 方法调用,负责返回数字的字符串表示形式。 实现方法通常涉及以下步骤:

    1. (可选)通过检查 provider 参数,确保此方法旨在以合法方式提供格式设置服务。 对于实现 IFormatProviderICustomFormatter 的格式设置对象,这涉及测试 provider 参数是否与当前格式设置对象相等。

    2. 确定格式设置对象是否应支持自定义格式说明符。 (例如,格式说明符“N”可能指示应以 NANP 格式输出美国电话号码,而“I”可能指示以 ITU-T 建议 E.123 格式进行输出。)如果使用格式说明符,则方法应处理特定格式说明符。 它会在 format 参数中传递给方法。 如果没有说明符,format 参数的值是 String.Empty

    3. 检索作为 arg 参数传递给方法的数值。 执行将它转换为其字符串表示形式所需的任何操作。

    4. 返回 arg 参数的字符串表示形式。

使用自定义数字格式设置对象

  1. 创建自定义格式设置类的新实例。

  2. 调用 String.Format(IFormatProvider, String, Object[]) 格式设置方法,同时向它传递自定义格式设置对象、格式设置说明符(或 String.Empty,如果未使用说明符的话),以及要设置格式的数值。

示例

下面的示例定义了一个名为 TelephoneFormatter 的自定义数值格式提供程序,该提供程序将代表美国电话号码的数字转化为它的 NANP 或 E.123 格式。 该方法处理两个格式说明符“N”(输出 NANP 格式)和“I”(输出国际 E.123 格式)。

using System;
using System.Globalization;

public class TelephoneFormatter : IFormatProvider, ICustomFormatter
{
   public object GetFormat(Type formatType)
   {
      if (formatType == typeof(ICustomFormatter))
         return this;
      else
         return null;
   }

   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
      // Check whether this is an appropriate callback
      if (! this.Equals(formatProvider))
         return null;

      // Set default format specifier
      if (string.IsNullOrEmpty(format))
         format = "N";

      string numericString = arg.ToString();

      if (format == "N")
      {
         if (numericString.Length <= 4)
            return numericString;
         else if (numericString.Length == 7)
            return numericString.Substring(0, 3) + "-" + numericString.Substring(3, 4);
         else if (numericString.Length == 10)
               return "(" + numericString.Substring(0, 3) + ") " +
                      numericString.Substring(3, 3) + "-" + numericString.Substring(6);
         else
            throw new FormatException(
                      string.Format("'{0}' cannot be used to format {1}.",
                                    format, arg.ToString()));
      }
      else if (format == "I")
      {
         if (numericString.Length < 10)
            throw new FormatException(string.Format("{0} does not have 10 digits.", arg.ToString()));
         else
            numericString = "+1 " + numericString.Substring(0, 3) + " " + numericString.Substring(3, 3) + " " + numericString.Substring(6);
      }
      else
      {
         throw new FormatException(string.Format("The {0} format specifier is invalid.", format));
      }
      return numericString;
   }
}

public class TestTelephoneFormatter
{
   public static void Main()
   {
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 0));
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 911));
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 8490216));
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));

      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 0));
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 911));
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 8490216));
      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));

      Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:I}", 4257884748));
   }
}
Public Class TelephoneFormatter : Implements IFormatProvider, ICustomFormatter
    Public Function GetFormat(formatType As Type) As Object _
                    Implements IFormatProvider.GetFormat
        If formatType Is GetType(ICustomFormatter) Then
            Return Me
        Else
            Return Nothing
        End If
    End Function

    Public Function Format(fmt As String, arg As Object, _
                           formatProvider As IFormatProvider) As String _
                    Implements ICustomFormatter.Format
        ' Check whether this is an appropriate callback             
        If Not Me.Equals(formatProvider) Then Return Nothing

        ' Set default format specifier             
        If String.IsNullOrEmpty(fmt) Then fmt = "N"

        Dim numericString As String = arg.ToString

        If fmt = "N" Then
            Select Case numericString.Length
                Case <= 4
                    Return numericString
                Case 7
                    Return Left(numericString, 3) & "-" & Mid(numericString, 4)
                Case 10
                    Return "(" & Left(numericString, 3) & ") " & _
                           Mid(numericString, 4, 3) & "-" & Mid(numericString, 7)
                Case Else
                    Throw New FormatException( _
                              String.Format("'{0}' cannot be used to format {1}.", _
                                            fmt, arg.ToString()))
            End Select
        ElseIf fmt = "I" Then
            If numericString.Length < 10 Then
                Throw New FormatException(String.Format("{0} does not have 10 digits.", arg.ToString()))
            Else
                numericString = "+1 " & Left(numericString, 3) & " " & Mid(numericString, 4, 3) & " " & Mid(numericString, 7)
            End If
        Else
            Throw New FormatException(String.Format("The {0} format specifier is invalid.", fmt))
        End If
        Return numericString
    End Function
End Class

Public Module TestTelephoneFormatter
    Public Sub Main
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 0))
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 911))
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 8490216))
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))

        Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 0))
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 911))
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 8490216))
        Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))

        Console.WriteLine(String.Format(New TelephoneFormatter, "{0:I}", 4257884748))
    End Sub
End Module

自定义数字格式提供程序只能与 String.Format(IFormatProvider, String, Object[]) 方法配合使用。 包含 IFormatProvider 类型参数的数字格式设置方法(如 ToString)的其他重载,都会向 IFormatProvider.GetFormat 实现传递表示 NumberFormatInfo 类型的 Type 对象。 此方法应返回 NumberFormatInfo 对象。 如果未返回,将会忽略自定义数字格式提供程序,而改用当前区域性的 NumberFormatInfo 对象。 在此示例中,TelephoneFormatter.GetFormat 方法检查方法参数,并在它表示除 ICustomFormatter 之外的类型时返回 null,从而处理它可能会被不恰当地传递给数字格式设置方法的情况。

如果自定义数字格式提供程序支持一组格式说明符,请确保提供在 String.Format(IFormatProvider, String, Object[]) 方法调用中使用的格式项没有格式说明符时的默认行为。 在示例中,“N”是默认格式说明符。 这使数字可以通过提供显式格式说明符来转换为格式化电话号码。 下面的示例演示了此类方法调用。

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))

但是它还允许在不存在格式说明符时进行转换。 下面的示例演示了此类方法调用。

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))

如果未定义默认格式说明符,ICustomFormatter.Format 方法实现应包含如下代码,以便 .NET 能够提供代码不支持的格式设置。

if (arg is IFormattable)
   s = ((IFormattable)arg).ToString(format, formatProvider);
else if (arg != null)
   s = arg.ToString();
If TypeOf (arg) Is IFormattable Then
    s = DirectCast(arg, IFormattable).ToString(fmt, formatProvider)
ElseIf arg IsNot Nothing Then
    s = arg.ToString()
End If

在此示例中,实现 ICustomFormatter.Format 的方法旨在用作 String.Format(IFormatProvider, String, Object[]) 方法的回调方法。 因此,它会检查 formatProvider 参数,以确定它是否包含对当前 TelephoneFormatter 对象的引用。 但是,也可以直接从代码调用该方法。 在这种情况下,可以使用 formatProvider 参数,提供用于提供区域性专用格式设置信息的 CultureInfoNumberFormatInfo 对象。