Практическое руководство. Определение и использование настраиваемых поставщиков числовых форматов

.NET обеспечивает расширенный контроль над строковым представлением числовых значений. Эта платформа поддерживает указанные далее возможности для настройки форматов числовых значений.

  • Строки стандартных числовых форматов, которые предоставляют стандартный набор форматов для преобразования чисел в их строковое представление. Вы можете использовать их с любым методом числового форматирования, например Decimal.ToString(String) с параметром format. Дополнительные сведения см. в статье Строки стандартных числовых форматов.

  • Строки настраиваемых числовых форматов, предоставляющих набор символов, которые могут быть объединены для определения описателей настраиваемого числового формата. Их можно использовать с любым методом числового форматирования, например Decimal.ToString(String) с параметром format. Дополнительные сведения см. в разделе Строки настраиваемых числовых форматов.

  • Настраиваемые объекты CultureInfo и NumberFormatInfo, которые определяют символы и шаблоны форматирования для отображения строковых представлений числовых значений. Вы можете использовать их с любым методом числового форматирования, например ToString с параметром provider. Как правило, параметр provider используется для указания форматирования, зависящего от языка и региональных параметров.

В некоторых случаях (например, когда приложению необходимо отобразить отформатированный номер учетной записи, идентификационный номер или почтовый индекс) эти три метода неприменимы. Кроме того, .NET позволяет определить объект форматирования, который не является объектом CultureInfo или NumberFormatInfo, для определения порядка форматирования числовых значений. Этот раздел содержит пошаговые инструкции по реализации таких объектов и пример форматирования телефонных номеров.

Определение поставщика пользовательского формата

  1. Определите класс, реализующий интерфейсы IFormatProvider и ICustomFormatter.

  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, чтобы убедиться, что метод действительно предназначен для форматирования. Для объектов форматирования, которые реализуют интерфейсы IFormatProvider и ICustomFormatter, нужно проверить еще и параметр provider, значение которого должно совпадать с текущим объектом форматирования.

    2. Определите, должен ли объект форматирования поддерживать описатели настраиваемого формата. (Например, описатель формата N может указывать, что номер телефона США должен быть выходным в формате NANP, а "I" может указывать выходные данные в формате ITU-T 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[]). Другие перегрузки методов числового форматирования (например, ToString), у которых параметр имеет тип IFormatProvider, передают в реализацию метода IFormatProvider.GetFormat объект Type, представляющий тип NumberFormatInfo. В ответ они ожидают получить объект 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 для предоставления объекта CultureInfo или NumberFormatInfo со сведениями о форматировании для выбранного языка и региональных параметров.