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

更新:2007 年 11 月

在 .NET Framework 中,您可以对数值的字符串表示形式施加广泛的控制。它支持用于自定义数值格式的下列功能:

  • 标准数值格式字符串,提供用于将数字转换为其字符串表示形式的预定义格式集。可以将这些字符串与带有 format 参数的任何数值格式化方法(例如 Decimal.ToString(String))一起使用。有关详细信息,请参见标准数字格式字符串

  • 自定义数值格式字符串,提供可通过组合来定义自定义数值格式说明符的符号集。它们也可以与带 format 参数的任何数值格式化方法(例如 Decimal.ToString(String))一起使用。有关详细信息,请参见自定义数字格式字符串

  • 自定义 CultureInfoNumberFormatInfo 对象,用于定义显示数值的字符串表示形式时使用的符号和格式模式。可以将这些字符串与带有 provider 参数的任何数值格式化方法(例如 ToString)一起使用。通常情况下,provider 参数用于指定区域性特定的格式。

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

定义自定义格式提供程序

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

  2. 实现 IFormatProvider.GetFormat 方法。GetFormat 是一个回调方法,格式化方法(例如 String.Format(IFormatProvider, String, array<Object[]) 方法)会调用该方法来检索真正负责执行自定义格式化的对象。典型的 GetFormat 实现可执行下列任务:

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

    2. 如果该参数的确表示 ICustomFormatter 接口,GetFormat 将返回一个实现负责提供自定义格式化的 ICustomFormatter 接口的对象。通常情况下,自定义格式化对象会返回自身。

    3. 如果该参数不表示 ICustomFormatter 接口,GetFormat 将返回 null。

  3. 实现 Format 方法。此方法由 String.Format(IFormatProvider, String, array<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, array<Object[]) 格式化方法,并向其传递自定义格式化对象、格式化说明符(如果未使用说明符,则为 String.Empty)以及要格式化的数值。

示例

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

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

自定义数值格式提供程序只能结合 String.Format(IFormatProvider, String, array<Object[]) 方法使用。带有 IFormatProvider 类型参数的数值格式化方法(例如 ToString)的其他重载都会向 IFormatProvider.GetFormat 实现传递一个表示 NumberFormatInfo 类型的 Type 对象。反过来,它们期望该方法返回 NumberFormatInfo 对象。如果未返回该对象,将忽略自定义数值格式提供程序,并改用当前区域性的 NumberFormatInfo 对象。在该示例中,TelephoneFormatter.GetFormat 方法会处理被不当传递给数值格式化方法的可能性,其方式是检查方法参数,如果方法参数表示的是 ICustomFormatter 以外的类型,它将返回 null。

如果自定义数值格式提供程序支持一组格式说明符,则应确保提供当 String.Format(IFormatProvider, String, array<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 Framework 可以提供您的代码所不支持的格式化。

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

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

编译代码

在命令行处使用 csc.exe 或 vb.exe 编译代码。若要在 Visual Studio 中编译代码,请将代码置于控制台应用程序项目模板中。

请参见

概念

格式化帮助主题