Compartilhar via


Práticas recomendadas para exibir e persistir dados formatados

Este artigo examina como os dados formatados, como dados numéricos e dados de data e hora, são tratados para exibição e armazenamento.

Ao desenvolver com o .NET, use a formatação sensível à cultura para exibir dados que não são de cadeia de caracteres, como números e datas, em uma interface do usuário. Use a formatação com a cultura invariável para persistir dados não textuais no formato de cadeia de caracteres. Não use a formatação sensível à cultura para persistir dados numéricos ou de data e hora no formato de cadeia de caracteres.

Exibir dados formatados

Quando você exibe dados que não são de cadeia de caracteres, como números e datas e horas para os usuários, formate-os usando as configurações culturais do usuário. Por padrão, todos os seguintes usam a cultura atual em operações de formatação:

  • Cadeias de caracteres interpoladas compatíveis com os compiladores C# e Visual Basic .
  • Operações de concatenação de cadeia de caracteres que usam os operadores de concatenação C# ou Visual Basic ou que chamam o String.Concat método diretamente.
  • O método String.Format.
  • Os métodos ToString dos tipos numéricos e dos tipos de data e hora.

Para especificar explicitamente que uma cadeia de caracteres deve ser formatada usando as convenções de uma cultura designada ou a cultura invariável, você pode fazer o seguinte:

  • Ao usar os métodos String.Format e ToString, chame uma sobrecarga que tenha um parâmetro provider, como String.Format(IFormatProvider, String, Object[]) ou DateTime.ToString(IFormatProvider), e passe a propriedade CultureInfo.CurrentCulture, uma instância CultureInfo que representa a cultura desejada, ou a propriedade CultureInfo.InvariantCulture.

  • Para concatenação de cadeia de caracteres, não permita que o compilador execute nenhuma conversão implícita. Em vez disso, execute uma conversão explícita, chamando uma sobrecarga ToString que tenha um parâmetro provider. Por exemplo, o compilador usa implicitamente a cultura atual ao converter um Double valor em uma cadeia de caracteres no seguinte código:

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

    Em vez disso, você pode especificar explicitamente a cultura cujas convenções de formatação são usadas na conversão chamando o Double.ToString(IFormatProvider) método, como o código a seguir:

    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)
    
  • Para interpolação de cadeia de caracteres, em vez de atribuir uma cadeia de caracteres interpolada a uma String instância, atribua-a a um FormattableString. Em seguida, você pode chamar seu FormattableString.ToString() método para produzir uma cadeia de caracteres de resultado que reflita as convenções da cultura atual ou pode chamar o FormattableString.ToString(IFormatProvider) método para produzir uma cadeia de caracteres de resultado que reflita as convenções de uma cultura especificada.

    Você também pode passar a cadeia de caracteres formatável para o método estático FormattableString.Invariant para produzir uma cadeia de caracteres de resultado que reflita as convenções da cultura invariável. O exemplo a seguir ilustra esta abordagem. (A saída do exemplo reflete uma cultura atual de 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
    

    Observação

    Se você estiver usando C# e formatando com a cultura invariável, é mais eficiente chamar String.Create(IFormatProvider, DefaultInterpolatedStringHandler) e passar CultureInfo.InvariantCulture como o primeiro parâmetro. Para obter mais informações, consulte Interpolação de cadeia de caracteres em C# 10 e .NET 6.

Manter dados formatados

Você pode persistir dados não cadeia de caracteres como dados binários ou como dados formatados. Se optar por salvá-los como dados formatados, você deverá chamar uma sobrecarga de método de formatação que inclua um parâmetro provider e passar para ele a propriedade CultureInfo.InvariantCulture. A cultura invariável fornece um formato consistente para dados formatados independentes da cultura e da máquina. Por outro lado, manter dados formatados usando culturas diferentes da cultura invariável tem uma série de limitações:

  • É provável que os dados sejam inutilizáveis se forem recuperados em um sistema que tenha uma cultura diferente ou se o usuário do sistema atual alterar a cultura atual e tentar recuperar os dados.
  • As propriedades de uma cultura em um computador específico podem ser diferentes dos valores padrão. A qualquer momento, um usuário pode personalizar as configurações de exibição sensíveis à cultura. Por isso, os dados formatados salvos em um sistema podem não ser legíveis depois que o usuário personaliza as configurações culturais. É provável que a portabilidade de dados formatados entre computadores seja ainda mais limitada.
  • Padrões internacionais, regionais ou nacionais que regem a formatação de números ou datas e horas mudam ao longo do tempo, e essas alterações são incorporadas às atualizações do sistema operacional Windows. Quando as convenções de formatação são alteradas, os dados formatados usando as convenções anteriores podem se tornar ilegíveis.

O exemplo a seguir ilustra a portabilidade limitada resultante do uso da formatação sensível à cultura para persistir os dados. O exemplo salva uma matriz de valores de data e hora em um arquivo. Elas são formatadas usando as convenções da cultura inglesa (Estados Unidos). Depois que o aplicativo altera a cultura atual para francês (Suíça), ele tenta ler os valores salvos usando as convenções de formatação da cultura atual. A tentativa de ler dois dos itens de dados gera uma FormatException exceção e a matriz de datas agora contém dois elementos incorretos que são iguais a 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
'

No entanto, se você substituir a propriedade CultureInfo.CurrentCulture por CultureInfo.InvariantCulture nas chamadas para DateTime.ToString(String, IFormatProvider) e DateTime.Parse(String, IFormatProvider), os dados de data e hora persistentes serão restaurados com êxito, como mostra a seguinte saída.

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