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


Рекомендации по отображению и сохранению форматированных данных

В этой статье рассматривается, как форматированные данные, такие как числовые данные и данные даты и времени, обрабатываются для отображения и хранения.

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

Отображение отформатированных данных

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

  • Интерполированные строки, поддерживаемые компиляторами C# и Visual Basic .
  • Операции объединения строк, использующие операторы объединения C# или Visual Basic или вызывающие метод напрямую String.Concat .
  • Метод String.Format.
  • ToString Методы числовых типов и типов даты и времени.

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

  • При использовании методов String.Format и ToString вызовите перегрузку, которая имеет параметр provider, например String.Format(IFormatProvider, String, Object[]) или DateTime.ToString(IFormatProvider), и передайте ему свойство CultureInfo.CurrentCulture, экземпляр CultureInfo, который представляет требуемую культуру или свойство CultureInfo.InvariantCulture.

  • Для объединения строк не разрешайте компилятору выполнять неявные преобразования. Вместо этого выполните явное преобразование, вызвав перегрузку ToString с параметром provider . Например, компилятор неявно использует текущую культуру при преобразовании значения Double в строку в следующем коде:

    string concat1 = "The amount is " + 126.03 + ".";
    Console.WriteLine(concat1);
    
    Dim concat1 As String = "The amount is " & 126.03 & "."
    Console.WriteLine(concat1)
    

    Вместо этого можно явно указать язык и региональные параметры, соглашения о форматировании которых используются в преобразовании, вызвав метод Double.ToString(IFormatProvider), как это делает следующий код.

    string concat2 = "The amount is " + 126.03.ToString(CultureInfo.InvariantCulture) + ".";
    Console.WriteLine(concat2);
    
    Dim concat2 As String = "The amount is " & 126.03.ToString(CultureInfo.InvariantCulture) & "."
    Console.WriteLine(concat2)
    
  • Для интерполяции строк, вместо того чтобы назначать интерполированную строку экземпляру String, назначьте её экземпляру FormattableString. Затем можно вызвать метод FormattableString.ToString() для создания результирующей строки, которая отражает соглашения текущей культуры, или вызвать метод FormattableString.ToString(IFormatProvider) для создания результирующей строки, которая отражает соглашения указанной культуры.

    Вы также можете передать строку форматирования в статический FormattableString.Invariant метод для создания результирующей строки, которая отражает соглашения инвариантной культуры. Этот подход показан в приведенном ниже примере. (Выходные данные из примера отражают текущую культуру en-US.)

    using System;
    using System.Globalization;
    
    class Program
    {
        static void Main()
        {
            Decimal value = 126.03m;
            FormattableString amount = $"The amount is {value:C}";
            Console.WriteLine(amount.ToString());
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")));
            Console.WriteLine(FormattableString.Invariant(amount));
        }
    }
    // The example displays the following output:
    //    The amount is $126.03
    //    The amount is 126,03 €
    //    The amount is ¤126.03
    
    Imports System.Globalization
    
    Module Program
        Sub Main()
            Dim value As Decimal = 126.03
            Dim amount As FormattableString = $"The amount is {value:C}" 
            Console.WriteLine(amount.ToString())
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")))
            Console.WriteLine(FormattableString.Invariant(amount))
        End Sub
    End Module
    ' The example displays the following output:
    '    The amount is $126.03
    '    The amount is 126,03 €
    '    The amount is ¤126.03
    

    Замечание

    Если вы используете C# и форматируете с использованием инвариантной культуры, лучше вызывать String.Create(IFormatProvider, DefaultInterpolatedStringHandler) и передавать CultureInfo.InvariantCulture как первый параметр. Дополнительные сведения см. в разделе Интерполяция строк в C# 10 и .NET 6.

Сохранение отформатированных данных

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

  • Данные, скорее всего, будут непригодными для использования, если они извлекаются в системе с другим языком и региональными параметрами, или если пользователь текущей системы изменяет текущий язык и региональные параметры и пытается получить данные.
  • Свойства культурных параметров на конкретном компьютере могут отличаться от стандартных значений. В любое время пользователь может настраивать параметры отображения с учетом языка и региональных параметров. Из-за этого форматированные данные, сохраненные в системе, могут быть недоступны для чтения после настройки параметров культуры. Переносимость форматированных данных на компьютерах, вероятно, будет еще более ограничена.
  • Международные, региональные или национальные стандарты, которые определяют форматирование чисел или дат и времени, со временем могут меняться, и эти изменения включаются в обновления операционной системы Windows. При изменении соглашений форматирования данные, отформатированные с помощью предыдущих соглашений, могут стать нечитаемыми.

В следующем примере показана ограниченная переносимость, которая приводит к использованию форматирования с учетом языка и региональных параметров для сохранения данных. В примере сохраняется массив значений даты и времени в файл. Они форматируются с помощью конвенций английского языка (Соединенные Штаты). После изменения текущих языковых и региональных настроек на французский (Швейцария) приложение пытается считать сохраненные значения, используя форматирование текущих языковых и региональных настроек. Попытка считывать два элемента данных создает FormatException исключение, а массив дат теперь содержит два неправильных элемента, равных MinValue.

using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;

public class Example
{
   private static string filename = @".\dates.dat";

   public static void Main()
   {
      DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0),
                           new DateTime(1818, 5, 5, 7, 19, 0),
                           new DateTime(1870, 4, 22, 23, 54, 0),
                           new DateTime(1890, 9, 8, 6, 47, 0),
                           new DateTime(1905, 2, 18, 15, 12, 0) };
      // Write the data to a file using the current culture.
      WriteData(dates);
      // Change the current culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
      // Read the data using the current culture.
      DateTime[] newDates = ReadData();
      foreach (var newDate in newDates)
         Console.WriteLine(newDate.ToString("g"));
   }

   private static void WriteData(DateTime[] dates)
   {
      StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);
      for (int ctr = 0; ctr < dates.Length; ctr++) {
         sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
         if (ctr < dates.Length - 1) sw.Write("|");
      }
      sw.Close();
   }

   private static DateTime[] ReadData()
   {
      bool exceptionOccurred = false;

      // Read file contents as a single string, then split it.
      StreamReader sr = new StreamReader(filename, Encoding.UTF8);
      string output = sr.ReadToEnd();
      sr.Close();

      string[] values = output.Split( new char[] { '|' } );
      DateTime[] newDates = new DateTime[values.Length];
      for (int ctr = 0; ctr < values.Length; ctr++) {
         try {
            newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
         }
         catch (FormatException) {
            Console.WriteLine($"Failed to parse {values[ctr]}");
            exceptionOccurred = true;
         }
      }
      if (exceptionOccurred) Console.WriteLine();
      return newDates;
   }
}
// The example displays the following output:
//       Failed to parse 4/22/1870 11:54 PM
//       Failed to parse 2/18/1905 3:12 PM
//
//       05.06.1758 21:26
//       05.05.1818 07:19
//       01.01.0001 00:00
//       09.08.1890 06:47
//       01.01.0001 00:00
//       01.01.0001 00:00
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading

Module Example
    Private filename As String = ".\dates.dat"

    Public Sub Main()
        Dim dates() As Date = {#5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _
                                #4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _
                                #2/18/1905 3:12PM#}
        ' Write the data to a file using the current culture.
        WriteData(dates)
        ' Change the current culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH")
        ' Read the data using the current culture.
        Dim newDates() As Date = ReadData()
        For Each newDate In newDates
            Console.WriteLine(newDate.ToString("g"))
        Next
    End Sub

    Private Sub WriteData(dates() As Date)
        Dim sw As New StreamWriter(filename, False, Encoding.Utf8)
        For ctr As Integer = 0 To dates.Length - 1
            sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
            If ctr < dates.Length - 1 Then sw.Write("|")
        Next
        sw.Close()
    End Sub

    Private Function ReadData() As Date()
        Dim exceptionOccurred As Boolean = False

        ' Read file contents as a single string, then split it.
        Dim sr As New StreamReader(filename, Encoding.Utf8)
        Dim output As String = sr.ReadToEnd()
        sr.Close()

        Dim values() As String = output.Split({"|"c})
        Dim newDates(values.Length - 1) As Date
        For ctr As Integer = 0 To values.Length - 1
            Try
                newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
            Catch e As FormatException
                Console.WriteLine("Failed to parse {0}", values(ctr))
                exceptionOccurred = True
            End Try
        Next
        If exceptionOccurred Then Console.WriteLine()
        Return newDates
    End Function
End Module
' The example displays the following output:
'       Failed to parse 4/22/1870 11:54 PM
'       Failed to parse 2/18/1905 3:12 PM
'       
'       05.06.1758 21:26
'       05.05.1818 07:19
'       01.01.0001 00:00
'       09.08.1890 06:47
'       01.01.0001 00:00
'       01.01.0001 00:00
'

Однако если вы замените свойство CultureInfo.CurrentCulture на CultureInfo.InvariantCulture в вызовах DateTime.ToString(String, IFormatProvider) и DateTime.Parse(String, IFormatProvider), сохраненные данные даты и времени успешно восстановлены, как показано в следующих выходных данных:

06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12