Составное форматирование

В качестве входных данных для составного форматирования в .NET используется список объектов и строка составного формата. Строка составного формата состоит из фиксированного текста, в который включены индексированные местозаполнители, которые называются элементами форматирования и соответствуют объектам из списка. Операция форматирования создает результирующую строку, состоящую из исходного фиксированного текста, в который включено строковое представление объектов из списка.

Важно!

Вместо использования строк составного формата можно использовать интерполированные строки, если их поддерживает язык и языковая версия, которые вы используете. Интерполированная строка — это строка, которая содержит интерполированные выражения. Каждое интерполированное выражение завершается значением выражения и включается в строку результатов, если строка назначена. Дополнительные сведения см. в разделе Интерполяция строк (справочник по C#) и Интерполированные строки (справочник по Visual Basic).

Возможность составного форматирования поддерживается следующими методами:

  • Метод String.Format, который возвращает отформатированную результирующую строку.

  • Метод StringBuilder.AppendFormat, который добавляет отформатированную результирующую строку в объект StringBuilder.

  • Некоторые перегруженные версии метода Console.WriteLine, которые отображают отформатированную результирующую строку в консоли.

  • Некоторые перегруженные версии метода TextWriter.WriteLine, которые записывают отформатированную результирующую строку в поток или файл. Классы, производные от TextWriter, например StreamWriter и HtmlTextWriter, также поддерживают эту функцию.

  • Метод Debug.WriteLine(String, Object[]), который выводит отформатированное сообщение в прослушиватели трассировки.

  • Методы Trace.TraceError(String, Object[]), Trace.TraceInformation(String, Object[]) и Trace.TraceWarning(String, Object[]), которые выводят отформатированные сообщения в прослушиватели трассировки.

  • Метод TraceSource.TraceInformation(String, Object[]), который записывает информационный метод в прослушиватели трассировки.

Строка составного формата

Строка составного формата и список объектов используются в качестве аргументов методов, поддерживающих составное форматирование. Строка составного формата состоит из блоков фиксированного текста числом от нуля и больше, перемежаемых одним или несколькими элементами форматирования. Фиксированным текстом может являться произвольная строка, а каждый элемент форматирования должен соответствовать объекту или упакованной структуре из списка. В ходе составного форматирования создается новая результирующая строка, в которой все элементы форматирования заменены на строковое представление соответствующих объектов из списка.

Рассмотрим следующий фрагмент кода Format.

string name = "Fred";
String.Format("Name = {0}, hours = {1:hh}", name, DateTime.Now);
Dim name As String = "Fred"
String.Format("Name = {0}, hours = {1:hh}", name, DateTime.Now)

Здесь "Name =" и ", hours =" — фиксированный текст. Элементы форматирования — это "{0}" c индексом 0, который соответствует объекту name, и "{1:hh}" с индексом 1, который соответствует объекту DateTime.Now.

Синтаксис элементов форматирования

Каждый элемент форматирования имеет следующий вид и состоит из следующих компонентов:

{index[,alignment][:formatString]}

Парные фигурные скобки ("{" и "}") здесь обязательны.

Индекс

Обязательный компонент index, также называемый описателем параметра, — это число, определяющее соответствующий объект из списка; индексация элементов ведется от нуля. Иными словами, элемент форматирования с индексом 0 отвечает за формат первого объекта в списке, элемент форматирования с индексом 1 служит для форматирования второго объекта в списке и т. д. В пример ниже входят четыре описателя параметров (от нуля до трех) для представления простых чисел меньше 10.

string primes;
primes = String.Format("Prime numbers less than 10: {0}, {1}, {2}, {3}",
                       2, 3, 5, 7 );
Console.WriteLine(primes);
// The example displays the following output:
//      Prime numbers less than 10: 2, 3, 5, 7
Dim primes As String
primes = String.Format("Prime numbers less than 10: {0}, {1}, {2}, {3}",
                       2, 3, 5, 7)
Console.WriteLine(primes)
' The example displays the following output:
'      Prime numbers less than 10: 2, 3, 5, 7

На один и тот же элемент в списке объектов может ссылаться сразу несколько элементов форматирования — достигается это путем задания одинакового описателя параметра. Например, одно и то же числовое значение можно перевести в формат шестнадцатеричного, экспоненциального и десятичного числа, задав строку составного формата, например "0x{0:X}{0:E}{0:N}", как показано в примере ниже.

string multiple = String.Format("0x{0:X} {0:E} {0:N}",
                                Int64.MaxValue);
Console.WriteLine(multiple);
// The example displays the following output:
//      0x7FFFFFFFFFFFFFFF 9.223372E+018 9,223,372,036,854,775,807.00
Dim multiple As String = String.Format("0x{0:X} {0:E} {0:N}",
                                       Int64.MaxValue)
Console.WriteLine(multiple)
' The example displays the following output:
'      0x7FFFFFFFFFFFFFFF 9.223372E+018 9,223,372,036,854,775,807.00

Любой элемент форматирования может ссылаться на произвольный объект списка. Например, если имеется три объекта, то можно отформатировать сначала второй, а затем первый и третий объекты, указав следующую строку составного форматирования: "{1}{0}{2}". Объекты, на которые не ссылаются элементы форматирования, пропускаются. Если описатель параметра ссылается на элемент за пределами списка объектов, то во время выполнения создается исключение FormatException.

Выравнивание

Необязательный компонент alignment — это целое число со знаком, которое служит для указания желательной ширины поля форматирования. Если значение alignment меньше длины форматируемой строки, то alignment пропускается, и в качестве значения ширины поля используется длина форматируемой строки. Форматируемые данные выравниваются в поле по правому краю, если alignment имеет положительное значение, или по левому краю, если alignment имеет отрицательное значение. При необходимости отформатированная строка дополняется пробелами. Если задано выравнивание , требуется запятая.

В примере ниже определяются два массива: один содержит имена сотрудников, а второй — количество часов, которые они проработали в течение двух недель. Строка составного формата выравнивает имена по левому краю 20-символьного поля, а часы работы — по правому краю 5-символьного поля. Обратите внимание, что строка стандартного формата "N1" также используется для форматирования часов с одной цифрой дробной части.

using System;

public class Example
{
   public static void Main()
   {
      string[] names = { "Adam", "Bridgette", "Carla", "Daniel",
                         "Ebenezer", "Francine", "George" };
      decimal[] hours = { 40, 6.667m, 40.39m, 82, 40.333m, 80,
                                 16.75m };

      Console.WriteLine("{0,-20} {1,5}\n", "Name", "Hours");
      for (int ctr = 0; ctr < names.Length; ctr++)
         Console.WriteLine("{0,-20} {1,5:N1}", names[ctr], hours[ctr]);
   }
}
// The example displays the following output:
//       Name                 Hours
//
//       Adam                  40.0
//       Bridgette              6.7
//       Carla                 40.4
//       Daniel                82.0
//       Ebenezer              40.3
//       Francine              80.0
//       George                16.8
Module Example
    Public Sub Main()
        Dim names() As String = {"Adam", "Bridgette", "Carla", "Daniel",
                                  "Ebenezer", "Francine", "George"}
        Dim hours() As Decimal = {40, 6.667d, 40.39d, 82, 40.333d, 80,
                                   16.75d}

        Console.WriteLine("{0,-20} {1,5}", "Name", "Hours")
        Console.WriteLine()
        For ctr As Integer = 0 To names.Length - 1
            Console.WriteLine("{0,-20} {1,5:N1}", names(ctr), hours(ctr))
        Next
    End Sub
End Module
' The example displays the following output:
'       Name                 Hours
'
'       Adam                  40.0
'       Bridgette              6.7
'       Carla                 40.4
'       Daniel                82.0
'       Ebenezer              40.3
'       Francine              80.0
'       George                16.8

Компонент строки формата

Необязательный компонент formatString — это строка формата, соответствующая типу форматируемого объекта. Если соответствующий объект является объектом DateTime, используется строка стандартного или настраиваемого формата чисел, а если соответствующий объект является значением перечисления, используется строка формата перечисления. Если компонент formatString не задан, то для числовых значений, значений даты и времени, а также перечислений используется общий формат ("G"). При использовании компонента formatString необходимо двоеточие.

В приведенной ниже таблице перечислены типы и категории типов в библиотеке классов .NET, которые поддерживают предопределенный набор строк формата, а также ссылки на разделы, в которых перечисляются поддерживаемые строки формата. Обратите внимание, что форматирование строк — это расширяемый механизм, который позволяет определять новые строки формата для всех существующих типов, а также определять набор строк формата, поддерживаемых прикладным типом. Подробнее см. в разделах, посвященных интерфейсам IFormattable и ICustomFormatter.

Тип или категория типов См.
Типы даты и времени (DateTime, DateTimeOffset) Строки стандартных форматов даты и времени

Строки настраиваемых форматов даты и времени
Типы перечисления (все типы, производные от System.Enum) Строки форматов перечисления
Числовые типы (BigInteger, Byte, Decimal, Double, Int16, Int32, Int64, SByte, Single, UInt16, UInt32, UInt64) Строки стандартных числовых форматов

Строки настраиваемых числовых форматов
Guid Guid.ToString(String)
TimeSpan Строки стандартного формата TimeSpan

Строки настраиваемого формата TimeSpan

Оформление фигурных скобок

Начало и конец элемента форматирования обозначаются соответственно открывающей и закрывающей фигурной скобкой. Это означает, что для вывода открывающих и закрывающих фигурных скобок необходимо использовать escape-последовательности. Для вывода открывающей или закрывающей фигурной скобки в фиксированном тексте следует поставить две открывающие или, соответственно, закрывающие фигурные скобки подряд ("{{" или "}}"). Фигурные скобки в элементе форматирования интерпретируются последовательно в порядке их обнаружения. Интерпретация вложенных скобок не поддерживается.

Порядок интерпретации скобок может привести к непредвиденным результатам. Например, рассмотрим элемент форматирования "{{{{0:D}}}}", который должен вывести открывающую фигурную скобку, числовое значение, отформатированное в десятичном виде, и закрывающую фигурную скобку. В действительности элемент форматирования будет интерпретирован следующим образом:

  1. Первые две открывающие фигурные скобки ("{{") составляют escape-последовательность, которая дает в итоге одиночную открывающую фигурную скобку.

  2. Следующие три знака ("{0:") воспринимаются как начало элемента форматирования.

  3. Следующий знак ("D") должен интерпретироваться как указатель на десятичный числовой формат, но стоящая за ним пара фигурных скобок ("}}") дает в результате одиночную фигурную скобку. Поскольку результирующая строка ("D}") не является стандартным описателем числового формата, то она будет интерпретирована как строка пользовательского формата, что приведет к выводу строки "D}".

  4. Последняя фигурная скобка ("}") интерпретируется как конец элемента форматирования.

  5. Итоговый результат, который будет выведен — строка "{D}". Числовое значение, которое требовалось отформатировать, выведено не будет.

Одним из способов избежать неправильной интерпретации фигурных скобок и элементов форматирования при написании кода является раздельное форматирование фигурных скобок и элементов форматирования. Это означает, что первая операция форматирования должна выводить строку с открывающей фигурной скобкой, следующая операция — результат обработки элемента форматирования, а последняя операция — строку с закрывающей фигурной скобкой. Этот подход показан в приведенном ниже примере.

int value = 6324;
string output = string.Format("{0}{1:D}{2}",
                             "{", value, "}");
Console.WriteLine(output);
// The example displays the following output:
//       {6324}
Dim value As Integer = 6324
Dim output As String = String.Format("{0}{1:D}{2}", _
                                     "{", value, "}")
Console.WriteLine(output)
' The example displays the following output:
'       {6324}

Порядок обработки

Если вызов метода составного форматирования содержит аргумент IFormatProvider, значение которого не равно null, среда выполнения вызывает метод IFormatProvider.GetFormat, чтобы запросить реализацию ICustomFormatter. Если метод может возвращать реализацию ICustomFormatter, он кэшируется на время вызова метода составного форматирования.

Каждое значение в списке параметров, соответствующее элементу форматирования, преобразуется в строку следующим образом:

  1. Если форматируемое значение является значением null, возвращается пустая строка String.Empty.

  2. Если реализация ICustomFormatter доступна, среда выполнения вызывает ее метод Format. Она передает в метод значение элемента форматирования formatString (при его наличии) или значение null (в случае его отсутствия) вместе с реализацией IFormatProvider. Если вызов метода ICustomFormatter.Format возвращает null, выполнение переходит к следующему шагу; в противном случае возвращается результат вызова ICustomFormatter.Format.

  3. Если значение реализует интерфейс IFormattable, вызывается метод ToString(String, IFormatProvider) этого интерфейса. Методу передается значение formatString (при его наличии в элементе форматирования) или значение null (в случае его отсутствия). Аргумент IFormatProvider определяется следующим образом:

    • Для числового значения, если вызывается метод составного форматирования с аргументом IFormatProvider, не равным null, то среда выполнения запрашивает объект NumberFormatInfo из метода IFormatProvider.GetFormat. Если не удается указать его, если значение аргумента равно nullили если метод составного форматирования не имеет IFormatProvider параметра, NumberFormatInfo используется объект для текущего языка и региональных параметров.

    • Для значения даты и времени, если вызывается метод составного форматирования с аргументом IFormatProvider, не равным null, то среда выполнения запрашивает объект DateTimeFormatInfo из метода IFormatProvider.GetFormat. Если не удается указать его, если значение аргумента равно nullили если метод составного форматирования не имеет IFormatProvider параметра, DateTimeFormatInfo используется объект для текущего языка и региональных параметров.

    • Для объектов других типов, если метод составного форматирования вызывается с аргументом IFormatProvider, то его значение передается непосредственно в реализацию IFormattable.ToString. В противном случае null передается в реализацию IFormattable.ToString.

  4. Вызывается метод ToString без параметров, который переопределяет Object.ToString() или наследует поведение базового класса. В этом случае строка формата, указанная в компоненте formatString в элементе форматирования (при его наличии), игнорируется.

После выполнения предшествующих шагов производится выравнивание.

Примеры кода

В приведенном ниже примере одна строка создается с помощью составного форматирования, а другая – с помощью метода ToString. Оба способа форматирования дают идентичные результаты.

string FormatString1 = String.Format("{0:dddd MMMM}", DateTime.Now);
string FormatString2 = DateTime.Now.ToString("dddd MMMM");
Dim FormatString1 As String = String.Format("{0:dddd MMMM}", DateTime.Now)
Dim FormatString2 As String = DateTime.Now.ToString("dddd MMMM")

Предположим, что сейчас май, а текущий день недели — четверг. Тогда значение обеих строк в предыдущем примере будет Thursday May для языка и региональных параметров "Английский (США)".

Метод Console.WriteLine предоставляет те же функциональные возможности, что и метод String.Format. Единственное различие между этими двумя методами состоит в том, что метод String.Format возвращает результаты в виде строки, а метод Console.WriteLine записывает результаты в поток вывода, связанный с объектом Console. В следующем примере для форматирования значения переменной Console.WriteLine в виде денежного значения используется метод MyInt.

int MyInt = 100;
Console.WriteLine("{0:C}", MyInt);
// The example displays the following output
// if en-US is the current culture:
//        $100.00
Dim MyInt As Integer = 100
Console.WriteLine("{0:C}", MyInt)
' The example displays the following output
' if en-US is the current culture:
'        $100.00

В следующем примере демонстрируется форматирование нескольких объектов, в том числе форматирование одного и того же объекта двумя разными способами.

string myName = "Fred";
Console.WriteLine(String.Format("Name = {0}, hours = {1:hh}, minutes = {1:mm}",
      myName, DateTime.Now));
// Depending on the current time, the example displays output like the following:
//    Name = Fred, hours = 11, minutes = 30
Dim myName As String = "Fred"
Console.WriteLine(String.Format("Name = {0}, hours = {1:hh}, minutes = {1:mm}", _
                  myName, DateTime.Now))
' Depending on the current time, the example displays output like the following:
'    Name = Fred, hours = 11, minutes = 30                 

В следующем примере показывается использование выравнивания при форматировании. Форматируемые аргументы разделены знаками вертикальной черты ("|"), подчеркивающими полученное выравнивание.

string myFName = "Fred";
string myLName = "Opals";
int myInt = 100;
string FormatFName = String.Format("First Name = |{0,10}|", myFName);
string FormatLName = String.Format("Last Name = |{0,10}|", myLName);
string FormatPrice = String.Format("Price = |{0,10:C}|", myInt);
Console.WriteLine(FormatFName);
Console.WriteLine(FormatLName);
Console.WriteLine(FormatPrice);
Console.WriteLine();

FormatFName = String.Format("First Name = |{0,-10}|", myFName);
FormatLName = String.Format("Last Name = |{0,-10}|", myLName);
FormatPrice = String.Format("Price = |{0,-10:C}|", myInt);
Console.WriteLine(FormatFName);
Console.WriteLine(FormatLName);
Console.WriteLine(FormatPrice);
// The example displays the following output on a system whose current
// culture is en-US:
//          First Name = |      Fred|
//          Last Name = |     Opals|
//          Price = |   $100.00|
//
//          First Name = |Fred      |
//          Last Name = |Opals     |
//          Price = |$100.00   |
Dim myFName As String = "Fred"
Dim myLName As String = "Opals"

Dim myInt As Integer = 100
Dim FormatFName As String = String.Format("First Name = |{0,10}|", myFName)
Dim FormatLName As String = String.Format("Last Name = |{0,10}|", myLName)
Dim FormatPrice As String = String.Format("Price = |{0,10:C}|", myInt)
Console.WriteLine(FormatFName)
Console.WriteLine(FormatLName)
Console.WriteLine(FormatPrice)
Console.WriteLine()

FormatFName = String.Format("First Name = |{0,-10}|", myFName)
FormatLName = String.Format("Last Name = |{0,-10}|", myLName)
FormatPrice = String.Format("Price = |{0,-10:C}|", myInt)
Console.WriteLine(FormatFName)
Console.WriteLine(FormatLName)
Console.WriteLine(FormatPrice)
' The example displays the following output on a system whose current
' culture is en-US:
'          First Name = |      Fred|
'          Last Name = |     Opals|
'          Price = |   $100.00|
'
'          First Name = |Fred      |
'          Last Name = |Opals     |
'          Price = |$100.00   |

См. также