Поделиться через


Метод System.String.Format

В этой статье приводятся дополнительные замечания к справочной документации по этому API.

Это важно

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

Примеры

Многочисленные примеры, вызывающие метод Format, разбросаны по всей этой статье. Вы также можете скачать полный набор примеров String.Format , включаемых в проект .NET Core для C#.

Ниже приведены некоторые примеры, включенные в статью:

Создать строку формата

Вставка строки
Элемент формата
Форматирование элементов с одинаковым индексом

Управление форматированным выводом

Форматирование элементов управления
Интервалы управления
Выравнивание элементов управления
Управление числом целочисленных цифр
Управление числом цифр после десятичного разделителя
Включите литеральные фигурные скобки в строку результата

Сделайте строки форматирования с учётом культурных особенностей

Сделайте строки форматирования учитывающими культурные особенности

Настройка операции форматирования

Настраиваемая операция форматирования
Провайдер перехвата и форматировщик римских чисел

Начало работы с методом String.Format

Используйте, String.Format если необходимо вставить значение объекта, переменной или выражения в другую строку. Например, можно вставить значение значения Decimal в строку, чтобы отобразить ее пользователю в виде одной строки:

Decimal pricePerOunce = 17.36m;
String s = String.Format("The current price is {0} per ounce.",
                         pricePerOunce);
Console.WriteLine(s);
// Result: The current price is 17.36 per ounce.
let pricePerOunce = 17.36m
String.Format("The current price is {0} per ounce.", pricePerOunce)
|> printfn "%s"
// Result: The current price is 17.36 per ounce.
Dim pricePerOunce As Decimal = 17.36D
Dim s As String = String.Format("The current price is {0} per ounce.",
                              pricePerOunce)
' Result: The current price is 17.36 per ounce.

И вы можете управлять форматированием этого значения:

Decimal pricePerOunce = 17.36m;
String s = String.Format("The current price is {0:C2} per ounce.",
                         pricePerOunce);
Console.WriteLine(s);
// Result if current culture is en-US:
//      The current price is $17.36 per ounce.
let pricePerOunce = 17.36m
String.Format("The current price is {0:C2} per ounce.", pricePerOunce)
|> printfn "%s"
// Result if current culture is en-US:
//      The current price is $17.36 per ounce.
Dim pricePerOunce As Decimal = 17.36D
Dim s As String = String.Format("The current price is {0:C2} per ounce.",
                              pricePerOunce)
' Result if current culture is en-US:
'      The current price is $17.36 per ounce.

Помимо форматирования, можно также управлять выравниванием и интервалом.

Вставка строки

String.Format начинается со строки форматирования, за которой следует один или несколько объектов или выражений, которые будут преобразованы в строки и вставлены в указанное место в строке форматирования. Рассмотрим пример.

decimal temp = 20.4m;
string s = String.Format("The temperature is {0}°C.", temp);
Console.WriteLine(s);
// Displays 'The temperature is 20.4°C.'
let temp = 20.4m
String.Format("The temperature is {0}°C.", temp)
|> printfn "%s"
// Displays 'The temperature is 20.4°C.'
Dim temp As Decimal = 20.4D
Dim s As String = String.Format("The temperature is {0}°C.", temp)
Console.WriteLine(s)
' Displays 'The temperature is 20.4°C.'

Строка {0} форматирования — это элемент формата. 0 — индекс объекта, строковое значение которого будет вставлено по этой позиции. (Индексы начинаются с 0.) Если объект, который нужно вставить, не является строкой, его ToString метод вызывается для преобразования его в один перед вставкой в результирующую строку.

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

string s = String.Format("At {0}, the temperature is {1}°C.",
                         DateTime.Now, 20.4);
Console.WriteLine(s);
// Output similar to: 'At 4/10/2015 9:29:41 AM, the temperature is 20.4°C.'
String.Format("At {0}, the temperature is {1}°C.", DateTime.Now, 20.4)
|> printfn "%s"
// Output similar to: 'At 4/10/2015 9:29:41 AM, the temperature is 20.4°C.'
Dim s As String = String.Format("At {0}, the temperature is {1}°C.",
                              Date.Now, 20.4)
' Output similar to: 'At 4/10/2015 9:29:41 AM, the temperature is 20.4°C.'

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

Форматирование элементов управления

Вы можете следовать индексу в элементе формата с строкой форматирования, чтобы управлять форматированием объекта. Например, {0:d} применяет строку формата "d" к первому объекту в списке объектов. Ниже приведен пример с одним объектом и двумя элементами формата:

string s = String.Format("It is now {0:d} at {0:t}", DateTime.Now);
Console.WriteLine(s);
// Output similar to: 'It is now 4/10/2015 at 10:04 AM'
String.Format("It is now {0:d} at {0:t}", DateTime.Now)
|> printfn "%s"
// Output similar to: 'It is now 4/10/2015 at 10:04 AM'
Dim s As String = String.Format("It is now {0:d} at {0:t}",
                              Date.Now)
' Output similar to: 'It is now 4/10/2015 at 10:04 AM'

Многочисленные типы поддерживают строки форматирования, включая все числовые типы (как стандартные , так инастраиваемые строки форматирования), все даты и время (стандартные инастраиваемые строки форматирования) и интервалы времени (как стандартные, так инастраиваемые строки форматирования), все типы перечислений и GUID. Вы также можете добавить поддержку строк форматирования в собственные типы.

Интервалы управления

Ширину строки, вставленной в результирующую строку, можно определить с помощью синтаксиса, например {0,12}, который вставляет 12-символьную строку. В этом случае строковое представление первого объекта выровнено вправо в поле шириной 12 символов. (Если строковое представление первого объекта имеет длину более 12 символов, тогда предпочтительная ширина поля игнорируется, и вся строка вставляется в результирующую строку.)

В следующем примере определяется 6-символьное поле для хранения строки "Год" и некоторых строк года, а также 15-символьного поля для хранения строки "Население" и некоторых данных о совокупности. Обратите внимание, что символы в этом поле выровнены по правому краю.

int[] years = { 2013, 2014, 2015 };
int[] population = { 1025632, 1105967, 1148203 };
var sb = new System.Text.StringBuilder();
sb.Append(String.Format("{0,6} {1,15}\n\n", "Year", "Population"));
for (int index = 0; index < years.Length; index++)
    sb.Append(String.Format("{0,6} {1,15:N0}\n", years[index], population[index]));

Console.WriteLine(sb);

// Result:
//      Year      Population
//
//      2013       1,025,632
//      2014       1,105,967
//      2015       1,148,203
open System
open System.Text
 
let years = [| 2013; 2014; 2015 |]
let population = [| 1025632; 1105967; 1148203 |]
let sb = StringBuilder()
sb.Append(String.Format("{0,6} {1,15}\n\n", "Year", "Population")) |> ignore
for i = 0 to years.Length - 1 do
   sb.Append(String.Format("{0,6} {1,15:N0}\n", years[i], population[i])) |> ignore

printfn $"{sb}"

// Result:
//      Year      Population
//
//      2013       1,025,632
//      2014       1,105,967
//      2015       1,148,203
Dim years() As Integer = {2013, 2014, 2015}
Dim population() As Integer = {1025632, 1105967, 1148203}
Dim sb As New StringBuilder()
sb.Append(String.Format("{0,6} {1,15}{2}{2}",
                       "Year", "Population", vbCrLf))
For index As Integer = 0 To years.Length - 1
    sb.AppendFormat("{0,6} {1,15:N0}{2}",
                  years(index), population(index), vbCrLf)
Next
' Result:
'      Year      Population
'
'      2013       1,025,632
'      2014       1,105,967
'      2015       1,148,203

Выравнивание элементов управления

По умолчанию строки выравниваются по правому краю в своём поле, если указана ширина поля. Чтобы выровнять строки по левому краю в поле, укажите ширину поля отрицательным знаком, например {0,-12}, для определения 12-символьного поля, выровненного по левому краю.

Следующий пример аналогичен предыдущему, но в нём выравниваются по левому краю как метки, так и данные.

int[] years = { 2013, 2014, 2015 };
int[] population = { 1025632, 1105967, 1148203 };
String s = String.Format("{0,-10} {1,-10}\n\n", "Year", "Population");
for (int index = 0; index < years.Length; index++)
    s += String.Format("{0,-10} {1,-10:N0}\n",
                       years[index], population[index]);
Console.WriteLine($"\n{s}");
// Result:
//    Year       Population
//
//    2013       1,025,632
//    2014       1,105,967
//    2015       1,148,203
let years = [| 2013; 2014; 2015 |]
let population = [| 1025632; 1105967; 1148203 |]
let mutable s = String.Format("{0,-10} {1,-10}\n\n", "Year", "Population")
for i = 0 to years.Length - 1 do
    s <- s + String.Format("{0,-10} {1,-10:N0}\n", years[i], population[i])
printfn $"\n{s}"
// Result:
//    Year       Population
//
//    2013       1,025,632
//    2014       1,105,967
//    2015       1,148,203
Dim years() As Integer = {2013, 2014, 2015}
Dim population() As Integer = {1025632, 1105967, 1148203}
Dim s As String = String.Format("{0,-10} {1,-10}{2}{2}",
                               "Year", "Population", vbCrLf)
For index As Integer = 0 To years.Length - 1
    s += String.Format("{0,-10} {1,-10:N0}{2}",
                     years(index), population(index), vbCrLf)
Next
' Result:
'    Year       Population
'
'    2013       1,025,632
'    2014       1,105,967
'    2015       1,148,203

String.Format использует функцию составного форматирования. Дополнительные сведения см. в разделе Составное форматирование.

Какой метод я должен вызвать?

Цель Метод вызова
Отформатируйте один или несколько объектов с помощью текущих культурных параметров. За исключением перегрузок, включающих параметр provider, остальные перегрузки Format включают параметр String, за которым следует один или несколько параметров объекта. Из-за этого вам не нужно решать, какую перегрузку Format хотите вызвать. Компилятор языка выбирает соответствующую перегрузку из перегрузок, которые не имеют provider параметра, в зависимости от списка аргументов. Например, если в списке аргументов есть пять аргументов, компилятор вызывает Format(String, Object[]) метод.
Отформатируйте один или несколько объектов, используя соглашения определенной культуры. За каждой перегрузкой Format, начинающейся с provider параметра, следует String параметр и один или более параметров объекта. Из-за этого вам не требуется определять конкретную Format перегрузку, которую вы планируете вызвать. Компилятор языка выбирает соответствующую перегрузку из перегрузок, имеющих provider параметр, в зависимости от списка аргументов. Например, если в списке аргументов есть пять аргументов, компилятор вызывает Format(IFormatProvider, String, Object[]) метод.
Выполните пользовательскую операцию форматирования либо с ICustomFormatter реализацией, либо IFormattable реализацией. Любой из четырех перегрузок с параметром provider . Компилятор выбирает соответствующую перегрузку из перегрузок, имеющих provider параметр, на основе списка аргументов.

Кратко о методе Format

Каждая перегрузка метода Format использует возможность составного форматирования для включения нулевых индексированных заполнителей, называемых элементами формата, в составную строку форматирования. Во время выполнения каждый элемент формата заменяется строковым представлением соответствующего аргумента в списке параметров. Если значение аргумента — это null, элемент форматирования заменяется на String.Empty. Например, следующий вызов Format(String, Object, Object, Object) метода включает строку формата с тремя элементами формата, {0}{1}и {2}списком аргументов с тремя элементами.

DateTime dat = new DateTime(2012, 1, 17, 9, 30, 0); 
string city = "Chicago";
int temp = -16;
string output = String.Format("At {0} in {1}, the temperature was {2} degrees.",
                              dat, city, temp);
Console.WriteLine(output);
// The example displays output like the following:
//    At 1/17/2012 9:30:00 AM in Chicago, the temperature was -16 degrees.
open System

let dat = DateTime(2012, 1, 17, 9, 30, 0) 
let city = "Chicago"
let temp = -16
String.Format("At {0} in {1}, the temperature was {2} degrees.", dat, city, temp)
|> printfn "%s"
// The example displays output like the following:
//    At 1/17/2012 9:30:00 AM in Chicago, the temperature was -16 degrees.
Dim dat As Date = #1/17/2012 9:30AM#
Dim city As String = "Chicago"
Dim temp As Integer = -16
Dim output As String = String.Format("At {0} in {1}, the temperature was {2} degrees.",
                                   dat, city, temp)
Console.WriteLine(output)
' The example displays the following output:
'    At 1/17/2012 9:30:00 AM in Chicago, the temperature was -16 degrees.

Элемент формата

Элемент формата имеет следующий синтаксис:

{index[,width][:formatString]}

Квадратные скобки указывают необязательные элементы. Необходимы открывающие и закрывающие фигурные скобки. (Чтобы включить литеральное открытие или закрытие фигурной скобки в строке формата, см. раздел Экранирование фигурных скобок в статье Составное форматирование.)

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

var value = String.Format("{0,-10:C}", 126347.89m);
Console.WriteLine(value);
open System

String.Format("{0,-10:C}", 126347.89m)         
|> printfn "%s"
String.Format("{0,-10:C}", 126347.89D)

Элемент формата содержит следующие элементы:

index
Отсчитываемый от нуля индекс аргумента, строковое представление которого должно быть включено в эту позицию в строке. Если этот аргумент имеет значение null, пустая строка будет включена в эту позицию в строке.

width
Необязательно. Целое число со знаком, указывающее общую длину поля, в которое вставляется аргумент, и сообщающее, выровнен ли он вправо (если целое число положительное) или влево (если целое число отрицательное). Если не указано width, строковое представление соответствующего аргумента вставляется в поле без начальных или конечных пробелов.

Если значение width меньше длины вставленного аргумента, игнорируется и width длина строкового представления аргумента используется в качестве ширины поля.

formatString
Необязательно. Строка, указывающая формат строки результата соответствующего аргумента. Если не указано formatString, вызывается метод без ToString параметров соответствующего аргумента для создания строкового представления. При указании formatString аргумент, на который ссылается форматный элемент, должен реализовать интерфейс IFormattable. Типы, поддерживающие строки формата, включают:

Однако любой пользовательский тип может реализовать IFormattable или расширить реализацию существующего типа IFormattable.

В следующем примере используются аргументы width и formatString для создания форматированных выходных данных.

// Create array of 5-tuples with population data for three U.S. cities, 1940-1950.
Tuple<string, DateTime, int, DateTime, int>[] cities = 
    { Tuple.Create("Los Angeles", new DateTime(1940, 1, 1), 1504277, 
                   new DateTime(1950, 1, 1), 1970358),
      Tuple.Create("New York", new DateTime(1940, 1, 1), 7454995, 
                   new DateTime(1950, 1, 1), 7891957),  
      Tuple.Create("Chicago", new DateTime(1940, 1, 1), 3396808, 
                   new DateTime(1950, 1, 1), 3620962),  
      Tuple.Create("Detroit", new DateTime(1940, 1, 1), 1623452, 
                   new DateTime(1950, 1, 1), 1849568) };

// Display header
var header = String.Format("{0,-12}{1,8}{2,12}{1,8}{2,12}{3,14}\n",
                              "City", "Year", "Population", "Change (%)");
Console.WriteLine(header);
foreach (var city in cities) {
   var output = String.Format("{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}",
                          city.Item1, city.Item2, city.Item3, city.Item4, city.Item5,
                          (city.Item5 - city.Item3)/ (double)city.Item3);
   Console.WriteLine(output);
}
// The example displays the following output:
//    City            Year  Population    Year  Population    Change (%)
//  
//    Los Angeles     1940   1,504,277    1950   1,970,358        31.0 %
//    New York        1940   7,454,995    1950   7,891,957         5.9 %
//    Chicago         1940   3,396,808    1950   3,620,962         6.6 %
//    Detroit         1940   1,623,452    1950   1,849,568        13.9 %
// Create a list of 5-tuples with population data for three U.S. cities, 1940-1950.
let cities = 
    [ "Los Angeles", DateTime(1940, 1, 1), 1504277, DateTime(1950, 1, 1), 1970358
      "New York", DateTime(1940, 1, 1), 7454995, DateTime(1950, 1, 1), 7891957
      "Chicago", DateTime(1940, 1, 1), 3396808, DateTime(1950, 1, 1), 3620962
      "Detroit", DateTime(1940, 1, 1), 1623452, DateTime(1950, 1, 1), 1849568 ]

// Display header
String.Format("{0,-12}{1,8}{2,12}{1,8}{2,12}{3,14}\n", "City", "Year", "Population", "Change (%)")
|> printfn "%s"

for name, year1, pop1, year2, pop2 in cities do
    String.Format("{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}",
                  name, year1, pop1, year2, pop2,
                  double (pop2 - pop1) / double pop1)
    |> printfn "%s"
// The example displays the following output:
//    City            Year  Population    Year  Population    Change (%)
//  
//    Los Angeles     1940   1,504,277    1950   1,970,358        31.0 %
//    New York        1940   7,454,995    1950   7,891,957         5.9 %
//    Chicago         1940   3,396,808    1950   3,620,962         6.6 %
//    Detroit         1940   1,623,452    1950   1,849,568        13.9 %
Module Example3
    Public Sub Main()
        ' Create array of 5-tuples with population data for three U.S. cities, 1940-1950.
        Dim cities() =
          {Tuple.Create("Los Angeles", #1/1/1940#, 1504277, #1/1/1950#, 1970358),
            Tuple.Create("New York", #1/1/1940#, 7454995, #1/1/1950#, 7891957),
            Tuple.Create("Chicago", #1/1/1940#, 3396808, #1/1/1950#, 3620962),
            Tuple.Create("Detroit", #1/1/1940#, 1623452, #1/1/1950#, 1849568)}

        ' Display header
        Dim header As String = String.Format("{0,-12}{1,8}{2,12}{1,8}{2,12}{3,14}",
                                           "City", "Year", "Population", "Change (%)")
        Console.WriteLine(header)
        Console.WriteLine()
        For Each city In cities
            Dim output = String.Format("{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}",
                                city.Item1, city.Item2, city.Item3, city.Item4, city.Item5,
                                (city.Item5 - city.Item3) / city.Item3)
            Console.WriteLine(output)
        Next
    End Sub
End Module
' The example displays the following output:
'    City            Year  Population    Year  Population    Change (%)
'    
'    Los Angeles     1940   1,504,277    1950   1,970,358        31.0 %
'    New York        1940   7,454,995    1950   7,891,957         5.9 %
'    Chicago         1940   3,396,808    1950   3,620,962         6.6 %
'    Detroit         1940   1,623,452    1950   1,849,568        13.9 %

Форматирование аргументов

Элементы форматирования обрабатываются последовательно с начала строки. Каждый элемент формата имеет индекс, соответствующий объекту в списке аргументов метода. Метод Format извлекает аргумент и получает строковое представление следующим образом:

  • Если аргумент null, метод вставляет String.Empty в строку результата. Вам не нужно беспокоиться об обработке NullReferenceException, связанной с пустыми аргументами.

  • При вызове перегрузки Format(IFormatProvider, String, Object[]), если реализация объекта provider в IFormatProvider.GetFormat возвращает реализацию ICustomFormatter, которая не равна NULL, аргумент передается в его метод ICustomFormatter.Format(String, Object, IFormatProvider). Если элемент формата содержит formatString аргумент, он передается в качестве первого аргумента методу. ICustomFormatter Если реализация доступна и создает непустую строку, эта строка возвращается в виде строкового представления аргумента; в противном случае выполняется следующий шаг.

  • Если аргумент реализует IFormattable интерфейс, его IFormattable.ToString реализация вызывается.

  • Метод аргумента без ToString параметров, который либо переопределяет, либо наследует реализацию базового класса, вызывается.

Для примера перехвата вызовов метода ICustomFormatter.Format, который позволяет увидеть, какую информацию метод Format передает методу форматирования для каждого элемента в строке составного формата, см. Пример: провайдер перехвата и форматировщик римских чисел.

Дополнительные сведения см. в разделе "Порядок обработки".

Форматирование элементов с одинаковым индексом

Метод Format создает FormatException исключение, если индекс элемента индекса больше или равен числу аргументов в списке аргументов. format Однако может содержать больше элементов формата, чем аргументы, если несколько элементов формата имеют один и тот же индекс. В вызове Format(String, Object) метода в следующем примере список аргументов имеет один аргумент, но строка формата включает два элемента формата: один отображает десятичное значение числа, а другой — шестнадцатеричное значение.

short[] values= { Int16.MinValue, -27, 0, 1042, Int16.MaxValue };
Console.WriteLine("{0,10}  {1,10}\n", "Decimal", "Hex");
foreach (short value in values)
{
   string formatString = String.Format("{0,10:G}: {0,10:X}", value);
   Console.WriteLine(formatString);
}   
// The example displays the following output:
//       Decimal         Hex
//    
//        -32768:       8000
//           -27:       FFE5
//             0:          0
//          1042:        412
//         32767:       7FFF
open System

let values= [| Int16.MinValue; -27s; 0s; 1042s; Int16.MaxValue |]
printfn "%10s  %10s\n" "Decimal" "Hex"
for value in values do
    String.Format("{0,10:G}: {0,10:X}", value)
    |> printfn "%s"
// The example displays the following output:
//       Decimal         Hex
//    
//        -32768:       8000
//           -27:       FFE5
//             0:          0
//          1042:        412
//         32767:       7FFF
Module Example1
    Public Sub Main()
        Dim values() As Short = {Int16.MinValue, -27, 0, 1042, Int16.MaxValue}
        Console.WriteLine("{0,10}  {1,10}", "Decimal", "Hex")
        Console.WriteLine()
        For Each value As Short In values
            Dim formatString As String = String.Format("{0,10:G}: {0,10:X}", value)
            Console.WriteLine(formatString)
        Next
    End Sub
End Module
' The example displays the following output:
'       Decimal         Hex
'    
'        -32768:       8000
'           -27:       FFE5
'             0:          0
'          1042:        412
'         32767:       7FFF

Формат и культура

Как правило, объекты в списке аргументов преобразуются в их строковые представления согласно культурным соглашениям текущего языка и региональных настроек, возвращаемых свойством CultureInfo.CurrentCulture. Это поведение можно контролировать, вызвав одну из перегрузок Format, включающую параметр provider. Параметр provider — это механизм, который предоставляет пользовательские IFormatProvider и культурно-специфические параметры форматирования, используемые для управления процессом форматирования.

Интерфейс IFormatProvider имеет один член, который отвечает за возврат объекта, GetFormatпредоставляющего сведения о форматировании. .NET имеет три IFormatProvider реализации, которые обеспечивают форматирование, зависящее от культуры:

  • CultureInfo. Его GetFormat метод возвращает объект, зависящий от культуры, для форматирования числовых значений и объект, зависящий от культуры, для форматирования значений даты и времени.
  • DateTimeFormatInfo, который используется для форматирования значений даты и времени с учётом культурных особенностей. Его GetFormat метод возвращает себя.
  • NumberFormatInfo, который используется для форматирования числовых значений в зависимости от культурных особенностей. Его GetFormat(Type) метод возвращает себя.

Пользовательские операции форматирования

Можно также вызвать любые специальные перегрузки метода Format, которые содержат параметр provider типа IFormatProvider, чтобы осуществлять пользовательские операции форматирования. Например, можно отформатировать целое число как идентификационный номер или номер телефона. Для выполнения настраиваемого форматирования аргумент provider должен реализовывать и интерфейс IFormatProvider, и интерфейс ICustomFormatter. Format Когда метод передает реализацию ICustomFormatter в качестве provider аргумента, Format метод вызывает свою IFormatProvider.GetFormat реализацию и запрашивает объект типаICustomFormatter. Затем он вызывает метод возвращаемого ICustomFormatter объекта Format для форматирования каждого элемента формата в составной строке, переданной ему.

Дополнительные сведения о предоставлении пользовательских решений форматирования см. в разделе "Практическое руководство. Определение и использование пользовательских поставщиков числовых форматов" иICustomFormatter. Пример преобразования целых чисел в форматированные пользовательские числа см. в примере: операция пользовательского форматирования. Пример преобразования байтов без знака в римские цифры см. в Пример: Поставщик перехвата и формататор римских цифр.

Пример: настраиваемая операция форматирования

В этом примере определяется поставщик формата, который форматирует целочисленное значение в виде номера учетной записи клиента в форме x-xxxxx-xx.

using System;

public class TestFormatter
{
   public static void Main()
   {
      int acctNumber = 79203159;
      Console.WriteLine(String.Format(new CustomerFormatter(), "{0}", acctNumber));
      Console.WriteLine(String.Format(new CustomerFormatter(), "{0:G}", acctNumber));
      Console.WriteLine(String.Format(new CustomerFormatter(), "{0:S}", acctNumber));
      Console.WriteLine(String.Format(new CustomerFormatter(), "{0:P}", acctNumber));
      try {
         Console.WriteLine(String.Format(new CustomerFormatter(), "{0:X}", acctNumber));
      }
      catch (FormatException e) {
         Console.WriteLine(e.Message);
      }
   }
}

public class CustomerFormatter : 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) 
   {                       
      if (! this.Equals(formatProvider))
      {
         return null;
      }
      else
      {
         if (String.IsNullOrEmpty(format)) 
            format = "G";
         
         string customerString = arg.ToString();
         if (customerString.Length < 8)
            customerString = customerString.PadLeft(8, '0');
         
         format = format.ToUpper();
         switch (format)
         {
            case "G":
               return customerString.Substring(0, 1) + "-" +
                                     customerString.Substring(1, 5) + "-" +
                                     customerString.Substring(6);
            case "S":                          
               return customerString.Substring(0, 1) + "/" +
                                     customerString.Substring(1, 5) + "/" +
                                     customerString.Substring(6);
            case "P":                          
               return customerString.Substring(0, 1) + "." +
                                     customerString.Substring(1, 5) + "." +
                                     customerString.Substring(6);
            default:
               throw new FormatException( 
                         String.Format("The '{0}' format specifier is not supported.", format));
         }
      }   
   }
}
// The example displays the following output:
//       7-92031-59
//       7-92031-59
//       7/92031/59
//       7.92031.59
//       The 'X' format specifier is not supported.
open System

type CustomerFormatter() = 
    interface IFormatProvider with
        member this.GetFormat(formatType) =
            if formatType = typeof<ICustomFormatter> then
                this
            else
                null

    interface ICustomFormatter with
        member this.Format(format, arg, formatProvider: IFormatProvider) = 
            if this.Equals formatProvider |> not then
                null
            else
                let format = 
                    if String.IsNullOrEmpty format then "G"
                    else format.ToUpper()
                
                let customerString = 
                    let s = string arg
                    if s.Length < 8 then
                        s.PadLeft(8, '0')
                    else s
                
                match format with
                | "G" ->
                    customerString.Substring(0, 1) + "-" +
                        customerString.Substring(1, 5) + "-" +
                        customerString.Substring 6
                | "S" ->                          
                    customerString.Substring(0, 1) + "/" +
                        customerString.Substring(1, 5) + "/" +
                        customerString.Substring 6
                | "P" ->                          
                    customerString.Substring(0, 1) + "." +
                        customerString.Substring(1, 5) + "." +
                        customerString.Substring 6
                | _ ->
                    raise (FormatException $"The '{format}' format specifier is not supported.")

let acctNumber = 79203159
String.Format(CustomerFormatter(), "{0}", acctNumber)
|> printfn "%s"
String.Format(CustomerFormatter(), "{0:G}", acctNumber)
|> printfn "%s"
String.Format(CustomerFormatter(), "{0:S}", acctNumber)
|> printfn "%s"
String.Format(CustomerFormatter(), "{0:P}", acctNumber)
|> printfn "%s"
try
    String.Format(CustomerFormatter(), "{0:X}", acctNumber)
    |> printfn "%s"
with :? FormatException as e ->
    printfn $"{e.Message}"

// The example displays the following output:
//       7-92031-59
//       7-92031-59
//       7/92031/59
//       7.92031.59
//       The 'X' format specifier is not supported.
Module TestFormatter
   Public Sub Main()
      Dim acctNumber As Integer = 79203159
      Console.WriteLine(String.Format(New CustomerFormatter, "{0}", acctNumber))
      Console.WriteLine(String.Format(New CustomerFormatter, "{0:G}", acctNumber))
      Console.WriteLine(String.Format(New CustomerFormatter, "{0:S}", acctNumber))
      Console.WriteLine(String.Format(New CustomerFormatter, "{0:P}", acctNumber))
      Try
         Console.WriteLine(String.Format(New CustomerFormatter, "{0:X}", acctNumber))
      Catch e As FormatException
         Console.WriteLine(e.Message)
      End Try   
   End Sub
End Module

Public Class CustomerFormatter : Implements IFormatProvider, ICustomFormatter
   Public Function GetFormat(type As Type) As Object  _
                   Implements IFormatProvider.GetFormat
      If type 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
      If Not Me.Equals(formatProvider) Then
         Return Nothing
      Else
         If String.IsNullOrEmpty(fmt) Then fmt = "G"
         
         Dim customerString As String = arg.ToString()
         if customerString.Length < 8 Then _
            customerString = customerString.PadLeft(8, "0"c)
         
         Select Case fmt
            Case "G"
               Return customerString.Substring(0, 1) & "-" & _
                                     customerString.Substring(1, 5) & "-" & _
                                     customerString.Substring(6)
            Case "S"                         
               Return customerString.Substring(0, 1) & "/" & _
                                     customerString.Substring(1, 5) & "/" & _
                                     customerString.Substring(6)
            Case "P"
               Return customerString.Substring(0, 1) & "." & _
                                     customerString.Substring(1, 5) & "." & _
                                     customerString.Substring(6)
            Case Else
               Throw New FormatException( _
                         String.Format("The '{0}' format specifier is not supported.", fmt))
         End Select                                                     
      End If   
   End Function
End Class
' The example displays the following output:
'       7-92031-59
'       7-92031-59
'       7/92031/59
'       7.92031.59
'       The 'X' format specifier is not supported.

Пример: перехватчик и форматировщик римских чисел

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

  • В нем отображаются параметры, переданные в реализацию ICustomFormatter.Format . Это позволяет узнать, какие параметры Format(IFormatProvider, String, Object[]) метод передает в пользовательскую реализацию форматирования для каждого объекта, который он пытается отформатировать. Это может быть полезно при отладке приложения.
  • Если объект, который должен быть отформатирован, является значением без знака байтов, которое должно быть отформатировано с помощью стандартной строки формата R, настраиваемое средство форматирования форматирует числовое значение как римское число.
using System;
using System.Globalization;

public class InterceptProvider : IFormatProvider, ICustomFormatter
{
   public object GetFormat(Type formatType)
   {
      if (formatType == typeof(ICustomFormatter))
         return this;
      else
         return null;
   }
   
   public string Format(String format, Object obj, IFormatProvider provider) 
   {
      // Display information about method call.
      string formatString = format ?? "<null>";
      Console.WriteLine("Provider: {0}, Object: {1}, Format String: {2}",
                        provider.GetType().Name, obj ?? "<null>", formatString);
                        
      if (obj == null) return String.Empty;
            
      // If this is a byte and the "R" format string, format it with Roman numerals.
      if (obj is Byte && formatString.ToUpper().Equals("R")) {
         Byte value = (Byte) obj;
         int remainder;
         int result;
         String returnString = String.Empty;

         // Get the hundreds digit(s)
         result = Math.DivRem(value, 100, out remainder);
         if (result > 0)  
            returnString = new String('C', result);
         value = (Byte) remainder;
         // Get the 50s digit
         result = Math.DivRem(value, 50, out remainder);
         if (result == 1)
            returnString += "L";
         value = (Byte) remainder;
         // Get the tens digit.
         result = Math.DivRem(value, 10, out remainder);
         if (result > 0)
            returnString += new String('X', result);
         value = (Byte) remainder; 
         // Get the fives digit.
         result = Math.DivRem(value, 5, out remainder);
         if (result > 0)
            returnString += "V";
         value = (Byte) remainder;
         // Add the ones digit.
         if (remainder > 0) 
            returnString += new String('I', remainder);
         
         // Check whether we have too many X characters.
         int pos = returnString.IndexOf("XXXX");
         if (pos >= 0) {
            int xPos = returnString.IndexOf("L"); 
            if (xPos >= 0 & xPos == pos - 1)
               returnString = returnString.Replace("LXXXX", "XC");
            else
               returnString = returnString.Replace("XXXX", "XL");   
         }
         // Check whether we have too many I characters
         pos = returnString.IndexOf("IIII");
         if (pos >= 0)
            if (returnString.IndexOf("V") >= 0)
               returnString = returnString.Replace("VIIII", "IX");
            else
               returnString = returnString.Replace("IIII", "IV");    

         return returnString; 
      }   

      // Use default for all other formatting.
      if (obj is IFormattable)
         return ((IFormattable) obj).ToString(format, CultureInfo.CurrentCulture);
      else
         return obj.ToString();
   }
}

public class Example
{
   public static void Main()
   {
      int n = 10;
      double value = 16.935;
      DateTime day = DateTime.Now;
      InterceptProvider provider = new InterceptProvider();
      Console.WriteLine(String.Format(provider, "{0:N0}: {1:C2} on {2:d}\n", n, value, day));
      Console.WriteLine(String.Format(provider, "{0}: {1:F}\n", "Today: ", 
                                      (DayOfWeek) DateTime.Now.DayOfWeek));
      Console.WriteLine(String.Format(provider, "{0:X}, {1}, {2}\n", 
                                      (Byte) 2, (Byte) 12, (Byte) 199));
      Console.WriteLine(String.Format(provider, "{0:R}, {1:R}, {2:R}\n", 
                                      (Byte) 2, (Byte) 12, (Byte) 199));
   }
}
// The example displays the following output:
//    Provider: InterceptProvider, Object: 10, Format String: N0
//    Provider: InterceptProvider, Object: 16.935, Format String: C2
//    Provider: InterceptProvider, Object: 1/31/2013 6:10:28 PM, Format String: d
//    10: $16.94 on 1/31/2013
//    
//    Provider: InterceptProvider, Object: Today: , Format String: <null>
//    Provider: InterceptProvider, Object: Thursday, Format String: F
//    Today: : Thursday
//    
//    Provider: InterceptProvider, Object: 2, Format String: X
//    Provider: InterceptProvider, Object: 12, Format String: <null>
//    Provider: InterceptProvider, Object: 199, Format String: <null>
//    2, 12, 199
//    
//    Provider: InterceptProvider, Object: 2, Format String: R
//    Provider: InterceptProvider, Object: 12, Format String: R
//    Provider: InterceptProvider, Object: 199, Format String: R
//    II, XII, CXCIX
open System
open System.Globalization

type InterceptProvider() =
    interface IFormatProvider with
        member this.GetFormat(formatType) =
            if formatType = typeof<ICustomFormatter> then
                this
            else
                null
    interface ICustomFormatter with
        member _.Format(format, obj, provider: IFormatProvider) = 
            // Display information about method call.
            let formatString =
                if format = null then "<null>" else format
            printfn $"Provider: {provider.GetType().Name}, Object: %A{obj}, Format String: %s{formatString}"
                                
            if obj = null then
                String.Empty
            else
                // If this is a byte and the "R" format string, format it with Roman numerals.
                match obj with
                | :? byte as value when formatString.ToUpper().Equals "R" -> 
                    let mutable returnString = String.Empty

                    // Get the hundreds digit(s)
                    let struct (result, remainder) = Math.DivRem(value, 100uy)
                    if result > 0uy then
                        returnString <- String('C', int result)
                    let value = byte remainder
                    // Get the 50s digit
                    let struct (result, remainder) = Math.DivRem(value, 50uy)
                    if result = 1uy then
                        returnString <- returnString + "L"
                    let value = byte remainder
                    // Get the tens digit.
                    let struct (result, remainder) = Math.DivRem(value, 10uy)
                    if result > 0uy then
                        returnString <- returnString + String('X', int result)
                    let value = byte remainder 
                    // Get the fives digit.
                    let struct (result, remainder) = Math.DivRem(value, 5uy)
                    if result > 0uy then
                        returnString <- returnString + "V"
                    let value = byte remainder
                    // Add the ones digit.
                    if remainder > 0uy then 
                        returnString <- returnString + String('I', int remainder)
                    
                    // Check whether we have too many X characters.
                    let pos = returnString.IndexOf "XXXX"
                    if pos >= 0 then
                        let xPos = returnString.IndexOf "L" 
                        returnString <-
                            if xPos >= 0 && xPos = pos - 1 then
                                returnString.Replace("LXXXX", "XC")
                            else
                                returnString.Replace("XXXX", "XL")   
                    // Check whether we have too many I characters
                    let pos = returnString.IndexOf "IIII"
                    if pos >= 0 then
                        returnString <-
                            if returnString.IndexOf "V" >= 0 then
                                returnString.Replace("VIIII", "IX")
                            else
                                returnString.Replace("IIII", "IV")    
                    returnString 

                // Use default for all other formatting.
                | :? IFormattable as x ->
                    x.ToString(format, CultureInfo.CurrentCulture)
                | _ ->
                    string obj

let n = 10
let value = 16.935
let day = DateTime.Now
let provider = InterceptProvider()
String.Format(provider, "{0:N0}: {1:C2} on {2:d}\n", n, value, day)
|> printfn "%s"
String.Format(provider, "{0}: {1:F}\n", "Today: ", DateTime.Now.DayOfWeek)
|> printfn "%s"
String.Format(provider, "{0:X}, {1}, {2}\n", 2uy, 12uy, 199uy)
|> printfn "%s"
String.Format(provider, "{0:R}, {1:R}, {2:R}\n", 2uy, 12uy, 199uy)
|> printfn "%s"
// The example displays the following output:
//    Provider: InterceptProvider, Object: 10, Format String: N0
//    Provider: InterceptProvider, Object: 16.935, Format String: C2
//    Provider: InterceptProvider, Object: 1/31/2013 6:10:28 PM, Format String: d
//    10: $16.94 on 1/31/2013
//    
//    Provider: InterceptProvider, Object: Today: , Format String: <null>
//    Provider: InterceptProvider, Object: Thursday, Format String: F
//    Today: : Thursday
//    
//    Provider: InterceptProvider, Object: 2, Format String: X
//    Provider: InterceptProvider, Object: 12, Format String: <null>
//    Provider: InterceptProvider, Object: 199, Format String: <null>
//    2, 12, 199
//    
//    Provider: InterceptProvider, Object: 2, Format String: R
//    Provider: InterceptProvider, Object: 12, Format String: R
//    Provider: InterceptProvider, Object: 199, Format String: R
//    II, XII, CXCIX
Imports System.Globalization

Public Class InterceptProvider : 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, obj As Object, provider As IFormatProvider) As String _
         Implements ICustomFormatter.Format

      Dim formatString As String = If(fmt IsNot Nothing, fmt, "<null>")
      Console.WriteLine("Provider: {0}, Object: {1}, Format String: {2}",
                        provider, If(obj IsNot Nothing, obj, "<null>"), formatString)

      If obj Is Nothing Then Return String.Empty
            
      ' If this is a byte and the "R" format string, format it with Roman numerals.
      If TypeOf(obj) Is Byte AndAlso formatString.ToUpper.Equals("R") Then
         Dim value As Byte = CByte(obj)
         Dim remainder As Integer
         Dim result As Integer
         Dim returnString As String = String.Empty

         ' Get the hundreds digit(s)
         result = Math.DivRem(value, 100, remainder)
         If result > 0 Then returnString = New String("C"c, result)
         value = CByte(remainder)
         ' Get the 50s digit
         result = Math.DivRem(value, 50, remainder)
         If result = 1 Then returnString += "L"
         value = CByte(remainder)
         ' Get the tens digit.
         result = Math.DivRem(value, 10, remainder)
         If result > 0 Then returnString += New String("X"c, result)
         value = CByte(remainder) 
         ' Get the fives digit.
         result = Math.DivRem(value, 5, remainder)
         If result > 0 Then returnString += "V"
         value = CByte(remainder)
         ' Add the ones digit.
         If remainder > 0 Then returnString += New String("I"c, remainder)
         
         ' Check whether we have too many X characters.
         Dim pos As Integer = returnString.IndexOf("XXXX")
         If pos >= 0 Then
            Dim xPos As Integer = returnString.IndexOf("L") 
            If xPos >= 0 And xPos = pos - 1 Then
               returnString = returnString.Replace("LXXXX", "XC")
            Else
               returnString = returnString.Replace("XXXX", "XL")   
            End If         
         End If
         ' Check whether we have too many I characters
         pos = returnString.IndexOf("IIII")
         If pos >= 0 Then
            If returnString.IndexOf("V") >= 0 Then
               returnString = returnString.Replace("VIIII", "IX")
            Else
               returnString = returnString.Replace("IIII", "IV")    
            End If
         End If
         Return returnString 
      End If   

      ' Use default for all other formatting.
      If obj Is GetType(IFormattable)
         Return CType(obj, IFormattable).ToString(fmt, CultureInfo.CurrentCulture)
      Else
         Return obj.ToString()
      End If
   End Function
End Class

Module Example
   Public Sub Main()
      Dim n As Integer = 10
      Dim value As Double = 16.935
      Dim day As DateTime = Date.Now
      Dim provider As New InterceptProvider()
      Console.WriteLine(String.Format(provider, "{0:N0}: {1:C2} on {2:d}", n, value, day))
      Console.WriteLine()
      Console.WriteLine(String.Format(provider, "{0}: {1:F}", "Today", 
                                      CType(Date.Now.DayOfWeek, DayOfWeek)))
      Console.WriteLine()
      Console.WriteLine(String.Format(provider, "{0:X}, {1}, {2}\n", 
                                      CByte(2), CByte(12), CByte(199)))
      Console.WriteLine()
      Console.WriteLine(String.Format(provider, "{0:R}, {1:R}, {2:R}", 
                                      CByte(2), CByte(12), CByte(199)))
   End Sub
End Module
' The example displays the following output:
'    Provider: InterceptProvider, Object: 10, Format String: N0
'    Provider: InterceptProvider, Object: 16.935, Format String: C2
'    Provider: InterceptProvider, Object: 1/31/2013 6:10:28 PM, Format String: d
'    10: $16.94 on 1/31/2013
'    
'    Provider: InterceptProvider, Object: Today: , Format String: <null>
'    Provider: InterceptProvider, Object: Thursday, Format String: F
'    Today: : Thursday
'    
'    Provider: InterceptProvider, Object: 2, Format String: X
'    Provider: InterceptProvider, Object: 12, Format String: <null>
'    Provider: InterceptProvider, Object: 199, Format String: <null>
'    2, 12, 199
'    
'    Provider: InterceptProvider, Object: 2, Format String: R
'    Provider: InterceptProvider, Object: 12, Format String: R
'    Provider: InterceptProvider, Object: 199, Format String: R
'    II, XII, CXCIX

Вопросы и ответы

Почему рекомендуется интерполяция строк для вызовов String.Format метода?

Интерполяция строк:

  • Более гибкий. Его можно использовать в любой строке без вызова метода, поддерживающего составное форматирование. В противном случае необходимо вызвать Format метод или другой метод, поддерживающий составное форматирование, например Console.WriteLine или StringBuilder.AppendFormat.
  • Легче читаемый Поскольку выражение для вставки в строку отображается в интерполированном выражении, а не в списке аргументов, интерполированные строки проще кодировать и читать. Интерполированные строки также можно использовать в операциях объединения строк для создания более краткого, более четкого кода.

Сравнение следующих двух примеров кода иллюстрирует превосходство интерполированных строк над объединением строк и вызовами составных методов форматирования. Использование нескольких операций объединения строк в следующем примере создает подробный и сложный код.

string[] names = { "Balto", "Vanya", "Dakota", "Samuel", "Koani", "Yiska", "Yuma" };
string output = names[0] + ", " + names[1] + ", " + names[2] + ", " +
                names[3] + ", " + names[4] + ", " + names[5] + ", " +
                names[6];

output += "\n";
var date = DateTime.Now;
output += String.Format("It is {0:t} on {0:d}. The day of the week is {1}.",
                        date, date.DayOfWeek);
Console.WriteLine(output);
// The example displays the following output:
//     Balto, Vanya, Dakota, Samuel, Koani, Yiska, Yuma
//     It is 10:29 AM on 1/8/2018. The day of the week is Monday.
open System

let names = [| "Balto"; "Vanya"; "Dakota"; "Samuel"; "Koani"; "Yiska"; "Yuma" |]
let output = 
    names[0] + ", " + names[1] + ", " + names[2] + ", " + 
    names[3] + ", " + names[4] + ", " + names[5] + ", " + 
    names[6] + "\n"

let date = DateTime.Now
output + String.Format("It is {0:t} on {0:d}. The day of the week is {1}.", date, date.DayOfWeek)
|> printfn "%s"
// The example displays the following output:
//     Balto, Vanya, Dakota, Samuel, Koani, Yiska, Yuma
//     It is 10:29 AM on 1/8/2018. The day of the week is Monday.

Module Example12
    Public Sub Main()
        Dim names = {"Balto", "Vanya", "Dakota", "Samuel", "Koani", "Yiska", "Yuma"}
        Dim output = names(0) + ", " + names(1) + ", " + names(2) + ", " +
                   names(3) + ", " + names(4) + ", " + names(5) + ", " +
                   names(6)

        output += vbCrLf
        Dim dat = DateTime.Now
        output += String.Format("It is {0:t} on {0:d}. The day of the week is {1}.",
                              dat, dat.DayOfWeek)
        Console.WriteLine(output)
    End Sub
End Module
' The example displays the following output:
'     Balto, Vanya, Dakota, Samuel, Koani, Yiska, Yuma
'     It is 10:29 AM on 1/8/2018. The day of the week is Monday.

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

string[] names = { "Balto", "Vanya", "Dakota", "Samuel", "Koani", "Yiska", "Yuma" };
string output = $"{names[0]}, {names[1]}, {names[2]}, {names[3]}, {names[4]}, " +
                $"{names[5]}, {names[6]}";

var date = DateTime.Now;
output += $"\nIt is {date:t} on {date:d}. The day of the week is {date.DayOfWeek}.";
Console.WriteLine(output);
// The example displays the following output:
//     Balto, Vanya, Dakota, Samuel, Koani, Yiska, Yuma
//     It is 10:29 AM on 1/8/2018. The day of the week is Monday.
open System

let names = [| "Balto"; "Vanya"; "Dakota"; "Samuel"; "Koani"; "Yiska"; "Yuma" |]
let output = $"{names[0]}, {names[1]}, {names[2]}, {names[3]}, {names[4]}, {names[5]}, {names[6]}"  

let date = DateTime.Now
output + $"\nIt is {date:t} on {date:d}. The day of the week is {date.DayOfWeek}."
|> printfn "%s" 
// The example displays the following output:
//     Balto, Vanya, Dakota, Samuel, Koani, Yiska, Yuma
//     It is 10:29 AM on 1/8/2018. The day of the week is Monday.

Module Example13
    Public Sub Main()
        Dim names = {"Balto", "Vanya", "Dakota", "Samuel", "Koani", "Yiska", "Yuma"}
        Dim output = $"{names(0)}, {names(1)}, {names(2)}, {names(3)}, {names(4)}, " +
                   $"{names(5)}, {names(6)}"

        Dim dat = DateTime.Now
        output += $"{vbCrLf}It is {dat:t} on {dat:d}. The day of the week is {dat.DayOfWeek}."
        Console.WriteLine(output)
    End Sub
End Module
' The example displays the following output:
'     Balto, Vanya, Dakota, Samuel, Koani, Yiska, Yuma
'     It is 10:29 AM on 1/8/2018. The day of the week is Monday.

Где можно найти предопределенные строки формата?

Как управлять выравниванием строк результатов, заменяющих элементы формата?

Общий синтаксис элемента формата:

{index[,width][: formatString]}

width — это целое число со знаком, определяющее ширину поля. Если это значение отрицательное, текст в текстовом поле будет выровнен по левому краю. Если условие положительное, текст выровнен по правому краю.

Как управлять числом цифр после десятичного разделителя?

Все строки стандартного числового формата, кроме "D" (который используется только с целыми числами), "G", "R" и "X", позволяют спецификатор точности, определяющий число десятичных цифр в результирующей строке. В следующем примере используются стандартные числовые строки формата для управления числом десятичных цифр в результирующем строке.

object[] values = { 1603, 1794.68235, 15436.14 };
string result;
foreach (var value in values)
{
    result = String.Format("{0,12:C2}   {0,12:E3}   {0,12:F4}   {0,12:N3}  {1,12:P2}\n",
                           Convert.ToDouble(value), Convert.ToDouble(value) / 10000);
    Console.WriteLine(result);
}
// The example displays output like the following:
//       $1,603.00     1.603E+003      1603.0000      1,603.000       16.03 %
//    
//       $1,794.68     1.795E+003      1794.6824      1,794.682       17.95 %
//    
//      $15,436.14     1.544E+004     15436.1400     15,436.140      154.36 %
open System

let values: obj list = [ 1603, 1794.68235, 15436.14 ]
for value in values do
   String.Format("{0,12:C2}   {0,12:E3}   {0,12:F4}   {0,12:N3}  {1,12:P2}\n", Convert.ToDouble(value), Convert.ToDouble(value) / 10000.)
   |> printfn "%s"
// The example displays output like the following:
//       $1,603.00     1.603E+003      1603.0000      1,603.000       16.03 %
//    
//       $1,794.68     1.795E+003      1794.6824      1,794.682       17.95 %
//    
//      $15,436.14     1.544E+004     15436.1400     15,436.140      154.36 %
Module Example7
    Public Sub Main()
        Dim values() As Object = {1603, 1794.68235, 15436.14}
        Dim result As String
        For Each value In values
            result = String.Format("{0,12:C2}   {0,12:E3}   {0,12:F4}   {0,12:N3}  {1,12:P2}",
                                value, CDbl(value) / 10000)
            Console.WriteLine(result)
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       $1,603.00     1.603E+003      1603.0000      1,603.000       16.03 %
'    
'       $1,794.68     1.795E+003      1794.6824      1,794.682       17.95 %
'    
'      $15,436.14     1.544E+004     15436.1400     15,436.140      154.36 %

Если вы используете настраиваемую строку числового формата, используйте описатель формата "0", чтобы управлять числом десятичных цифр в результирующих строках, как показано в следующем примере.

decimal value = 16309.5436m;
string result = String.Format("{0,12:#.00000} {0,12:0,000.00} {0,12:000.00#}",
                              value);
Console.WriteLine(result);
// The example displays the following output:
//        16309.54360    16,309.54    16309.544
let value = 16309.5436m
String.Format("{0,12:#.00000} {0,12:0,000.00} {0,12:000.00#}", value)
|> printfn "%s"
// The example displays the following output:
//        16309.54360    16,309.54    16309.544
Module Example8
    Public Sub Main()
        Dim value As Decimal = 16309.5436D
        Dim result As String = String.Format("{0,12:#.00000} {0,12:0,000.00} {0,12:000.00#}",
                                           value)
        Console.WriteLine(result)
    End Sub
End Module
' The example displays the following output:
'    16309.54360    16,309.54    16309.544

Как управлять числом целочисленных цифр?

По умолчанию операции форматирования отображают только ненулевые целые числа. При форматировании целых чисел можно использовать описатель точности со строками стандартного формата "D" и "X" для управления числом цифр.

int value = 1326;
string result = String.Format("{0,10:D6} {0,10:X8}", value);
Console.WriteLine(result);
// The example displays the following output:
//     001326   0000052E
open System

let value = 1326
String.Format("{0,10:D6} {0,10:X8}", value)
|> printfn "%s"
// The example displays the following output:
//     001326   0000052E
Module Example10
    Public Sub Main()
        Dim value As Integer = 1326
        Dim result As String = String.Format("{0,10:D6} {0,10:X8}", value)
        Console.WriteLine(result)
    End Sub
End Module
' The example displays the following output:
'       001326   0000052E

Целое число или число с плавающей запятой можно дополнить начальными нулями для создания строки результата с указанным числом разрядов с помощью описателя пользовательского числового формата "0", как показано в следующем примере.

int value = 16342;
string result = String.Format("{0,18:00000000} {0,18:00000000.000} {0,18:000,0000,000.0}",
                              value);
Console.WriteLine(result);
// The example displays the following output:
//           00016342       00016342.000    0,000,016,342.0
open System

let value = 16342
String.Format("{0,18:00000000} {0,18:00000000.000} {0,18:000,0000,000.0}", value)
|> printfn "%s"
// The example displays the following output:
//           00016342       00016342.000    0,000,016,342.0
Module Example9
    Public Sub Main()
        Dim value As Integer = 16342
        Dim result As String = String.Format("{0,18:00000000} {0,18:00000000.000} {0,18:000,0000,000.0}",
                                           value)
        Console.WriteLine(result)
    End Sub
End Module
' The example displays the following output:
'           00016342       00016342.000    0,000,016,342.0

Сколько элементов я могу включить в список форматов?

Нет практического ограничения. Второй параметр Format(IFormatProvider, String, Object[]) метода помечен ParamArrayAttribute атрибутом, что позволяет включать список разделителей или массив объектов в качестве списка форматов.

Как включить литеральные скобки ("{" и "}") в результирующую строку?

Например, как запретить следующему вызову метода вызвать FormatException исключение?

result = String.Format("The text has {0} '{' characters and {1} '}' characters.",
                       nOpen, nClose);
let result = 
    String.Format("The text has {0} '{' characters and {1} '}' characters.", nOpen, nClose)
result = String.Format("The text has {0} '{' characters and {1} '}' characters.",
                 nOpen, nClose)

Одна открывающая или закрывающая фигурная скобка всегда рассматривается как начало или конец элемента форматирования. Чтобы его интерпретировали буквально, необходимо экранирование. Вы экранируете фигурную скобку, добавив еще одну фигурную скобку ("{{" и "}}", а не "{" и "}"), как в следующем вызове метода:

string result;
int nOpen = 1;
int nClose = 2;
result = String.Format("The text has {0} '{{' characters and {1} '}}' characters.",
                       nOpen, nClose);
Console.WriteLine(result);
let result =
    String.Format("The text has {0} '{{' characters and {1} '}}' characters.", nOpen, nClose)
result = String.Format("The text has {0} '{{' characters and {1} '}}' characters.",
                 nOpen, nClose)

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

string result;
int nOpen = 1;
int nClose = 2;
result = String.Format("The text has {0} '{1}' characters and {2} '{3}' characters.",
                       nOpen, "{", nClose, "}");
Console.WriteLine(result);
let result =
    String.Format("The text has {0} '{1}' characters and {2} '{3}' characters.", nOpen, "{", nClose, "}")
result = String.Format("The text has {0} '{1}' characters and {2} '{3}' characters.",
                 nOpen, "{", nClose, "}")

Почему вызов метода String.Format вызывает исключение FormatException?

Наиболее распространенной причиной исключения является то, что индекс элемента формата не соответствует объекту в списке форматов. Обычно это означает, что индексы элементов форматирования были неправильно указаны или вы забыли включить объект в список форматов. При попытке включить неискаченный левый или правый символ фигурной скобки FormatExceptionтакже вызывается. Иногда исключение является результатом опечатки; Например, типичная ошибка заключается в неправильном вводе "[" (левая скобка) вместо "{" (левая скобка).

Если метод Format(System.IFormatProvider,System.String,System.Object[]) поддерживает массивы параметров, почему мой код создает исключение при использовании массива?

Например, следующий код вызывает FormatException исключение:

Random rnd = new Random();
int[] numbers = new int[4];
int total = 0;
for (int ctr = 0; ctr <= 2; ctr++)
{
    int number = rnd.Next(1001);
    numbers[ctr] = number;
    total += number;
}
numbers[3] = total;
Console.WriteLine($"{numbers} + {1} + {2} = {3}");
open System

let rnd = Random()
let mutable total = 0
let numbers = Array.zeroCreate<int> 4
for i = 0 to 2 do
   let number = rnd.Next 1001
   numbers[i] <- number
   total <- total + number
numbers[3] <- total
Console.WriteLine("{0} + {1} + {2} = {3}", numbers)
Imports System.Collections.Generic

Module Example5
    Public Sub Main()
        Dim rnd As New Random()
        Dim numbers(3) As Integer
        Dim total As Integer = 0
        For ctr = 0 To 2
            Dim number As Integer = rnd.Next(1001)
            numbers(ctr) = number
            total += number
        Next
        numbers(3) = total
        Console.WriteLine("{0} + {1} + {2} = {3}", numbers)
    End Sub
End Module

Это проблема разрешения перегрузки компилятора. Так как компилятор не может преобразовать массив целых чисел в массив объектов, он обрабатывает целый массив как один аргумент, поэтому вызывает Format(String, Object) метод. Исключение возникает из-за того, что имеется четыре элемента формата, но только один элемент в списке форматирования.

Так как ни Visual Basic, ни C# не могут преобразовать целый массив в массив объектов, перед вызовом Format(String, Object[]) метода необходимо выполнить преобразование самостоятельно. В следующем примере представлена одна реализация.

Random rnd = new Random();
int[] numbers = new int[4];
int total = 0;
for (int ctr = 0; ctr <= 2; ctr++)
{
    int number = rnd.Next(1001);
    numbers[ctr] = number;
    total += number;
}
numbers[3] = total;
object[] values = new object[numbers.Length];
numbers.CopyTo(values, 0);
Console.WriteLine($"{values} + {1} + {2} = {3}");
open System

let rnd = Random()
let numbers = Array.zeroCreate<int> 4
let mutable total = 0
for i = 0 to 2 do
   let number = rnd.Next 1001
   numbers[i] <- number
   total <- total + number
numbers[3] <- total
let values = Array.zeroCreate<obj> numbers.Length
numbers.CopyTo(values, 0)
Console.WriteLine("{0} + {1} + {2} = {3}", values)
Imports System.Collections.Generic

Module Example6
    Public Sub Main()
        Dim rnd As New Random()
        Dim numbers(3) As Integer
        Dim total As Integer = 0
        For ctr = 0 To 2
            Dim number As Integer = rnd.Next(1001)
            numbers(ctr) = number
            total += number
        Next
        numbers(3) = total
        Dim values(numbers.Length - 1) As Object
        numbers.CopyTo(values, 0)
        Console.WriteLine("{0} + {1} + {2} = {3}", values)
    End Sub
End Module