作法:定義和使用自訂數值格式提供者

.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),全都會將代表 NumberFormatInfo 類型的 Type 物件傳遞給 IFormatProvider.GetFormat 實作。 接著,它們預期方法會傳回 NumberFormatInfo 物件。 如果未傳回,則會忽略自訂數值格式提供者,並使用目前文化特性的 NumberFormatInfo 物件來取代。 在此範例中,TelephoneFormatter.GetFormat 方法會檢查方法參數並傳回 null (如果其代表 ICustomFormatter 以外的類型),藉以處理可能不當傳遞至數值格式化方法的情況。

當自訂數值格式提供者支援一組格式規範時,如果 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 物件,該物件會提供文化特性特定的格式資訊。