Meilleures pratiques pour l’affichage et la persistance des données mises en forme

Cet article explique comment les données mises en forme, telles que les données numériques et les données de date et d’heure, sont gérées pour l’affichage et le stockage.

Lorsque vous développez avec .NET, utilisez une mise en forme sensible à la culture pour afficher des données non-chaînes, telles que des nombres et des dates, dans une interface utilisateur. Utilisez le formatage avec la culture invariante pour conserver des données non textuelles sous forme textuelle. N’utilisez pas de mise en forme sensible à la culture pour conserver les données numériques ou de date et heure sous forme de chaîne.

Afficher les données mises en forme

Lorsque vous affichez des données non chaînes telles que des nombres et des dates et des heures pour les utilisateurs, mettez-les en forme à l’aide des paramètres culturels de l’utilisateur. Par défaut, tous les éléments suivants utilisent la culture actuelle dans les opérations de mise en forme :

  • Chaînes interpolées prises en charge par les compilateurs C# et Visual Basic .
  • Opérations de concaténation de chaîne qui utilisent les opérateurs de concaténation C# ou Visual Basic ou qui appellent directement la String.Concat méthode.
  • Méthode String.Format.
  • Les méthodes ToString des types numériques et des types de dates et d'heures.

Pour spécifier explicitement qu’une chaîne doit être mise en forme à l’aide des conventions d’une culture désignée ou de la culture invariante, vous pouvez effectuer les opérations suivantes :

  • Lorsque vous utilisez les méthodes String.Format et ToString, appelez une surcharge qui a un paramètre provider, telles que String.Format(IFormatProvider, String, Object[]) ou DateTime.ToString(IFormatProvider), et transmettez-lui la propriété CultureInfo.CurrentCulture, une instance CultureInfo qui représente la culture souhaitée, ou la propriété CultureInfo.InvariantCulture.

  • Pour la concaténation de chaînes, n’autorisez pas le compilateur à effectuer des conversions implicites. Au lieu de cela, effectuez une conversion explicite en appelant une surcharge ToString ayant un paramètre provider. Par exemple, le compilateur utilise implicitement la culture actuelle lors de la conversion d’une Double valeur en chaîne dans le code suivant :

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

    Au lieu de cela, vous pouvez spécifier explicitement la culture dont les conventions de mise en forme sont utilisées dans la conversion en appelant la Double.ToString(IFormatProvider) méthode, comme le fait le code suivant :

    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)
    
  • Pour l’interpolation de chaîne, plutôt que d’affecter une chaîne interpolée à une String instance, affectez-la à un FormattableString. Vous pouvez ensuite appeler sa FormattableString.ToString() méthode pour produire une chaîne de résultat qui reflète les conventions de la culture actuelle, ou vous pouvez appeler la FormattableString.ToString(IFormatProvider) méthode pour produire une chaîne de résultat qui reflète les conventions d’une culture spécifiée.

    Vous pouvez également transmettre la chaîne mise en forme à la méthode statique FormattableString.Invariant pour produire une chaîne de résultat qui reflète les conventions de la culture invariante. L’exemple suivant illustre cette approche. (La sortie de l’exemple correspond à la culture actuelle 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
    

    Remarque

    Si vous utilisez C# et la mise en forme à l’aide de la culture invariante, il est plus performant d’appeler String.Create(IFormatProvider, DefaultInterpolatedStringHandler) et de passer CultureInfo.InvariantCulture pour le premier paramètre. Pour plus d’informations, consultez Interpolation de chaîne en C# 10 et .NET 6.

Conserver les données mises en forme

Vous pouvez conserver des données non chaînes en tant que données binaires ou en tant que données mises en forme. Si vous choisissez de l’enregistrer en tant que données mises en forme, vous devez appeler une surcharge de méthode de mise en forme qui inclut un provider paramètre et lui passer la propriété CultureInfo.InvariantCulture. La culture invariante fournit un format cohérent pour les données mises en forme indépendantes de la culture et de l’ordinateur. En revanche, la persistance des données mises en forme à l’aide de cultures autres que la culture invariante présente un certain nombre de limitations :

  • Les données sont susceptibles d’être inutilisables si elles sont récupérées sur un système qui a une culture différente, ou si l’utilisateur du système actuel modifie la culture actuelle et tente de récupérer les données.
  • Les propriétés d’une culture sur un ordinateur spécifique peuvent différer des valeurs standard. À tout moment, un utilisateur peut personnaliser les paramètres d’affichage sensibles à la culture. En raison de cela, les données mises en forme enregistrées sur un système peuvent ne pas être lisibles une fois que l’utilisateur personnalise les paramètres culturels. La portabilité des données mises en forme sur plusieurs ordinateurs est susceptible d’être encore plus limitée.
  • Normes internationales, régionales ou nationales qui régissent la mise en forme des nombres ou des dates et des heures changent au fil du temps, et ces modifications sont incorporées dans les mises à jour du système d’exploitation Windows. Lorsque les conventions de mise en forme changent, les données mises en forme à l’aide des conventions précédentes peuvent devenir non lisibles.

L’exemple suivant illustre la portabilité limitée résultant de l’utilisation d’une mise en forme sensible à la culture pour conserver les données. L’exemple enregistre un tableau de valeurs de date et d’heure dans un fichier. Elles sont mises en forme à l’aide des conventions de la culture anglaise (États-Unis). Après que l’application change la culture actuelle en Français (Suisse), elle tente de lire les valeurs enregistrées à l’aide des conventions de mise en forme de la culture actuelle. La tentative de lecture de deux éléments de données déclenche une exception FormatException, et le tableau de dates contient maintenant deux éléments incorrects qui sont égaux à 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
'

Toutefois, si vous remplacez les CultureInfo.CurrentCulture propriétés par CultureInfo.InvariantCulture dans les appels vers DateTime.ToString(String, IFormatProvider) et DateTime.Parse(String, IFormatProvider), les données de date et d’heure enregistrées sont restaurées, comme le montre la sortie suivante :

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