Globalizzazione

La globalizzazione comporta la progettazione e lo sviluppo di un'app internazionalizzata che supporti interfacce localizzate e dati locali per utenti in più impostazioni cultura. Prima di iniziare la fase di progettazione, è necessario determinare quali impostazioni cultura verranno supportate dall'app. Sebbene un'app sia destinata per impostazione predefinita a singole impostazioni cultura o a una singola area, è possibile progettarla e scriverla in modo che possa essere facilmente estesa agli utenti di altre impostazioni cultura o aree.

Gli sviluppatori partono sempre da supposizione su interfacce utente e dati che sono relativi alle impostazioni cultura. Ad esempio, per uno sviluppatore di lingua inglese negli Stati Uniti, la serializzazione di data e ora sotto forma di stringa nel formato MM/dd/yyyy hh:mm:ss sembra perfettamente ragionevole. Tuttavia, la deserializzazione di questa stringa in un sistema con impostazioni cultura diverse potrebbe generare un'eccezione FormatException o dati non accurati. La globalizzazione consente di identificare queste supposizioni relative alle impostazioni cultura e di garantire che non influiscano sulla progettazione o sul codice dell'app.

Questo articolo illustra alcuni degli aspetti principali da considerare e le procedure consigliate da seguire quando si gestiscono stringhe, valori di data e ora e valori numerici in un'app globalizzata.

Stringhe

La gestione di caratteri e stringhe è un elemento cruciale nella globalizzazione, in quanto le impostazioni cultura o area possono usare caratteri e set di caratteri diversi e ordinarli in modo diverso. Questa sezione include suggerimenti per l'uso delle stringhe nelle app globalizzate.

Usare Unicode internamente

Per impostazione predefinita, .NET usa stringhe Unicode. Una stringa Unicode è costituita da zero, da uno o più oggetti Char, ognuno dei quali rappresenta un'unità di codice UTF-16. Esiste una rappresentazione in formato Unicode di quasi tutti i caratteri inclusi nei set di caratteri usati nel mondo.

In molti sistemi operativi e applicazioni, incluso il sistema operativo Windows, è possibile usare anche tabelle codici per rappresentare i set di caratteri. Le tabelle codici in genere contengono i valori ASCII standard compresi tra 0x00 e 0x7F ed eseguono il mapping degli altri caratteri ai valori rimanenti compresi tra 0x80 e 0xFF. L'interpretazione dei valori da 0x80 a 0xFF dipende dalla tabella codici specifica. Per questo motivo, se possibile, è consigliabile evitare l'uso di tabelle codici in un'app globalizzata.

Nell'esempio seguente vengono illustrati i rischi di interpretazione dei dati della tabella codici quando la tabella codici predefinita in un sistema è diversa dalla tabella codici in cui i sono stati salvati i dati. Per simulare questo scenario, l'esempio specifica in modo esplicito tabelle codici diverse. Nell'esempio viene definita prima di tutto una matrice costituita dai caratteri maiuscoli dell'alfabeto greco. I caratteri vengono codificati in una matrice di byte usando la tabella codici 737 (nota anche come Greco MS-DOS) e la matrice di byte viene salvata in un file. Se viene recuperato il file e viene decodificata la relativa matrice di byte tramite la tabella codici 737, vengono ripristinati i caratteri originali. Tuttavia, se viene recuperato il file e viene decodificata la relativa matrice di byte usando la tabella codici 1252 (o Windows-1252, che rappresenta i caratteri nell'alfabeto latino), i caratteri originali vengono persi.

using System;
using System.IO;
using System.Text;

public class Example
{
    public static void CodePages()
    {
        // Represent Greek uppercase characters in code page 737.
        char[] greekChars =
        {
            'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ',
            'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', 'Π',
            'Ρ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω'
        };

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

        Encoding cp737 = Encoding.GetEncoding(737);
        int nBytes = cp737.GetByteCount(greekChars);
        byte[] bytes737 = new byte[nBytes];
        bytes737 = cp737.GetBytes(greekChars);
        // Write the bytes to a file.
        FileStream fs = new FileStream(@".\\CodePageBytes.dat", FileMode.Create);
        fs.Write(bytes737, 0, bytes737.Length);
        fs.Close();

        // Retrieve the byte data from the file.
        fs = new FileStream(@".\\CodePageBytes.dat", FileMode.Open);
        byte[] bytes1 = new byte[fs.Length];
        fs.Read(bytes1, 0, (int)fs.Length);
        fs.Close();

        // Restore the data on a system whose code page is 737.
        string data = cp737.GetString(bytes1);
        Console.WriteLine(data);
        Console.WriteLine();

        // Restore the data on a system whose code page is 1252.
        Encoding cp1252 = Encoding.GetEncoding(1252);
        data = cp1252.GetString(bytes1);
        Console.WriteLine(data);
    }
}

// The example displays the following output:
//       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
//       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

Imports System.IO
Imports System.Text

Module Example
    Public Sub CodePages()
        ' Represent Greek uppercase characters in code page 737.
        Dim greekChars() As Char = {"Α"c, "Β"c, "Γ"c, "Δ"c, "Ε"c, "Ζ"c, "Η"c, "Θ"c,
                                     "Ι"c, "Κ"c, "Λ"c, "Μ"c, "Ν"c, "Ξ"c, "Ο"c, "Π"c,
                                     "Ρ"c, "Σ"c, "Τ"c, "Υ"c, "Φ"c, "Χ"c, "Ψ"c, "Ω"c}

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)

        Dim cp737 As Encoding = Encoding.GetEncoding(737)
        Dim nBytes As Integer = CInt(cp737.GetByteCount(greekChars))
        Dim bytes737(nBytes - 1) As Byte
        bytes737 = cp737.GetBytes(greekChars)
        ' Write the bytes to a file.
        Dim fs As New FileStream(".\CodePageBytes.dat", FileMode.Create)
        fs.Write(bytes737, 0, bytes737.Length)
        fs.Close()

        ' Retrieve the byte data from the file.
        fs = New FileStream(".\CodePageBytes.dat", FileMode.Open)
        Dim bytes1(CInt(fs.Length - 1)) As Byte
        fs.Read(bytes1, 0, CInt(fs.Length))
        fs.Close()

        ' Restore the data on a system whose code page is 737.
        Dim data As String = cp737.GetString(bytes1)
        Console.WriteLine(data)
        Console.WriteLine()

        ' Restore the data on a system whose code page is 1252.
        Dim cp1252 As Encoding = Encoding.GetEncoding(1252)
        data = cp1252.GetString(bytes1)
        Console.WriteLine(data)
    End Sub
End Module
' The example displays the following output:
'       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
'       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

L'uso di Unicode assicura che le stesse unità di codice eseguono sempre il mapping agli stessi caratteri e che gli stessi caratteri eseguono sempre il mapping alle stesse matrici di byte.

Usare file di risorse

Anche se si sviluppa un'app destinata a singole impostazioni cultura o aree, è necessario usare file di risorse per archiviare le stringhe e altre risorse visualizzate nell'interfaccia utente. Si raccomanda di non aggiungerli mai direttamente al codice. L'uso di file di risorse presenta una serie di vantaggi:

  • Tutte le stringhe si trovano in un'unica posizione. Non è necessario eseguire la ricerca in tutto il codice di origine per identificare le stringhe da modificare per una lingua o impostazioni cultura specifiche.
  • Non è necessario duplicare le stringhe. Gli sviluppatori che non usano file di risorse spesso definiscono la stessa stringa in più file di codice sorgente. Questa duplicazione aumenta la probabilità che una o più istanze possano essere ignorate quando viene modificata una stringa.
  • È possibile includere risorse non di tipo stringa, ad esempio immagini o dati binari, nel file di risorse anziché archiviarle in un file autonomo separato, per poterle recuperare facilmente.

L'uso di file di risorse presenta vantaggi specifici se si crea un'app localizzata. Quando si distribuiscono le risorse in assembly satellite, Common Language Runtime seleziona automaticamente una risorsa di impostazioni cultura appropriata basata sulle impostazioni cultura dell'interfaccia utente corrente come specificato dalla proprietà CultureInfo.CurrentUICulture. Purché venga definita una risorsa specifica delle impostazioni cultura adatta e si crei correttamente un'istanza di un oggetto ResourceManager o si usi una classe di risorse fortemente tipizzata, il runtime gestisce i dettagli relativi al recupero delle risorse appropriate.

Per altre informazioni sulla creazione di file di risorse, vedere Creazione di file di risorse. Per informazioni sulla creazione e distribuzione di assembly satellite, vedere Creare assembly satellite e Creare un pacchetto e distribuire le risorse.

Eseguire la ricerca e il confronto di stringhe

Quando possibile, si raccomanda di gestire le stringhe come unità intere, anziché come serie di caratteri singoli. Ciò è particolarmente importante quando si ordinano o cercano le sottostringhe, per impedire problemi associati all'analisi dei caratteri combinati.

Suggerimento

È possibile usare la classe StringInfo per gestire gli elementi di testo anziché i singoli caratteri in una stringa.

Nelle ricerche e nei confronti di stringhe, un errore comune consiste nel gestire la stringa come una raccolta di caratteri, ognuno dei quali è rappresentato da un oggetto Char. Infatti, un singolo carattere può essere costituito da uno, due o più oggetti Char. Caratteri di questo tipo vengono trovati più frequentemente nelle stringhe delle impostazioni cultura i cui alfabeti sono costituiti da caratteri esterni all'intervallo di caratteri latini di base Unicode (da U+0021 a U+007E). Nell'esempio seguente si tenta di trovare l'indice del carattere LETTERA LATINA A MAIUSCOLA CON accento GRAVE (U+00C0) in una stringa. Tuttavia, questo carattere può essere rappresentato in due modi diversi: come singola unità di codice (U+00C0) o come carattere composito (due unità di codice: U+0041 e U+0300). In questo caso, il carattere è rappresentato nell'istanza della stringa da due oggetti Char, U+0041 e U+0300. Il codice di esempio chiama gli overload String.IndexOf(Char) e String.IndexOf(String) per trovare la posizione del carattere incluso nell'istanza della stringa, ma questi restituiscono risultati diversi. La prima chiamata al metodo dispone di un argomento Char; esegue un confronto ordinale e pertanto non può trovare una corrispondenza. La seconda chiamata dispone di un argomento String; esegue un confronto dipendente dalle impostazioni cultura e pertanto trova una corrispondenza.

using System;
using System.Globalization;
using System.Threading;

public class Example17
{
    public static void Main17()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("pl-PL");
        string composite = "\u0041\u0300";
        Console.WriteLine("Comparing using Char:   {0}", composite.IndexOf('\u00C0'));
        Console.WriteLine("Comparing using String: {0}", composite.IndexOf("\u00C0"));
    }
}

// The example displays the following output:
//       Comparing using Char:   -1
//       Comparing using String: 0
Imports System.Globalization
Imports System.Threading

Module Example17
    Public Sub Main17()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("pl-PL")
        Dim composite As String = ChrW(&H41) + ChrW(&H300)
        Console.WriteLine("Comparing using Char:   {0}", composite.IndexOf(ChrW(&HC0)))
        Console.WriteLine("Comparing using String: {0}", composite.IndexOf(ChrW(&HC0).ToString()))
    End Sub
End Module
' The example displays the following output:
'       Comparing using Char:   -1
'       Comparing using String: 0

È possibile evitare parte dell'ambiguità di questo esempio (chiamate a due overload simili di un metodo che restituisce risultati diversi) chiamando un overload che include un parametro StringComparison, ad esempio il metodo String.IndexOf(String, StringComparison) o String.LastIndexOf(String, StringComparison).

Tuttavia, le ricerche non sono sempre dipendenti dalle impostazioni cultura. Se lo scopo della ricerca è prendere una decisione relativa alla sicurezza oppure consentire o impedire l'accesso ad alcune risorse, il confronto deve essere ordinale, come descritto nella sezione successiva.

Testare l'uguaglianza delle stringhe

Se si vuole verificare l'uguaglianza tra due stringhe anziché determinarne la modalità di confronto nell'ordinamento, usare il metodo String.Equals anziché un metodo di confronto di stringhe come String.Compare o CompareInfo.Compare.

I confronti di uguaglianza in genere vengono eseguiti i per accedere ad alcune risorse in modo condizionale. Ad esempio, è possibile eseguire un confronto di uguaglianza per verificare una password o l'esistenza di un file. Confronti non linguistici di questo tipo devono essere ordinali anziché dipendenti dalle impostazioni cultura. In genere, è consigliabile chiamare il metodo String.Equals(String, StringComparison) dell'istanza o il metodo String.Equals(String, String, StringComparison) statico con un valore StringComparison.Ordinal per stringhe quali le password e un valore StringComparison.OrdinalIgnoreCase per stringhe quali i nomi file o gli URI.

I confronti di uguaglianza talvolta comportano ricerche o confronti delle sottostringhe anziché chiamate al metodo String.Equals. In alcuni casi, si potrebbe usare la ricerca di una sottostringa per determinare se la sottostringa è uguale a un'altra stringa. Se lo scopo di questo confronto non è linguistico, anche la ricerca deve essere ordinale anziché dipendente dalle impostazioni cultura.

Nell'esempio seguente viene illustrato il rischio di una ricerca dipendente dalle impostazioni cultura su dati non linguistici. Il metodo AccessesFileSystem è progettato per impedire l'accesso al file system agli URI che iniziano con la sottostringa "FILE". A tale scopo, esegue un confronto dipendente dalle impostazioni cultura, con distinzione tra maiuscole e minuscole, dell'inizio dell'URI con la stringa "FILE". Poiché un URI che accede al file system può iniziare con "FILE:" o "file:", il presupposto implicito è che "i" (U+0069) sia sempre l'equivalente minuscolo di "I" (U+0049). Tuttavia, in turco e in azerbaigiano, la versione in maiuscolo di "i" è "İ" (U+0130). A causa di questa discrepanza, il confronto dipendente dalle impostazioni cultura consente l'accesso al file system anche se non dovesse essere consentito.

using System;
using System.Globalization;
using System.Threading;

public class Example10
{
    public static void Main10()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR");
        string uri = @"file:\\c:\users\username\Documents\bio.txt";
        if (!AccessesFileSystem(uri))
            // Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.");
        else
            // Prohibit access.
            Console.WriteLine("Access is not allowed.");
    }

    private static bool AccessesFileSystem(string uri)
    {
        return uri.StartsWith("FILE", true, CultureInfo.CurrentCulture);
    }
}

// The example displays the following output:
//         Access is allowed.
Imports System.Globalization
Imports System.Threading

Module Example10
    Public Sub Main10()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR")
        Dim uri As String = "file:\\c:\users\username\Documents\bio.txt"
        If Not AccessesFileSystem(uri) Then
            ' Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.")
        Else
            ' Prohibit access.
            Console.WriteLine("Access is not allowed.")
        End If
    End Sub

    Private Function AccessesFileSystem(uri As String) As Boolean
        Return uri.StartsWith("FILE", True, CultureInfo.CurrentCulture)
    End Function
End Module
' The example displays the following output:
'       Access is allowed.

È possibile evitare questo problema eseguendo un confronto ordinale che ignora le maiuscole e minuscole, come illustrato nell'esempio di seguito.

using System;
using System.Globalization;
using System.Threading;

public class Example11
{
    public static void Main11()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR");
        string uri = @"file:\\c:\users\username\Documents\bio.txt";
        if (!AccessesFileSystem(uri))
            // Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.");
        else
            // Prohibit access.
            Console.WriteLine("Access is not allowed.");
    }

    private static bool AccessesFileSystem(string uri)
    {
        return uri.StartsWith("FILE", StringComparison.OrdinalIgnoreCase);
    }
}

// The example displays the following output:
//         Access is not allowed.
Imports System.Globalization
Imports System.Threading

Module Example11
    Public Sub Main11()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR")
        Dim uri As String = "file:\\c:\users\username\Documents\bio.txt"
        If Not AccessesFileSystem(uri) Then
            ' Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.")
        Else
            ' Prohibit access.
            Console.WriteLine("Access is not allowed.")
        End If
    End Sub

    Private Function AccessesFileSystem(uri As String) As Boolean
        Return uri.StartsWith("FILE", StringComparison.OrdinalIgnoreCase)
    End Function
End Module
' The example displays the following output:
'       Access is not allowed.

Ordinare le stringhe

In genere, le stringhe ordinate da visualizzare nell'interfaccia utente devono essere ordinate in base alle impostazioni cultura. Nella maggior parte dei casi questi confronti di stringhe vengono gestiti in modo implicito da .NET quando si chiama un metodo di ordinamento stringhe, ad esempio Array.Sort o List<T>.Sort. Per impostazione predefinita, le stringhe vengono ordinate usando le convenzioni di ordinamento delle impostazioni cultura correnti. Nell'esempio seguente viene illustrata la differenza quando viene ordinata una matrice di stringhe usando le convenzioni delle impostazioni cultura inglese (Stati Uniti) e le impostazioni cultura svedese (Svezia).

using System;
using System.Globalization;
using System.Threading;

public class Example18
{
    public static void Main18()
    {
        string[] values = { "able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio" };
        // Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        // Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values);
        string[] enValues = (String[])values.Clone();

        // Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("sv-SE");
        Array.Sort(values);
        string[] svValues = (String[])values.Clone();

        // Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}\n", "Position", "en-US", "sv-SE");
        for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues[ctr], svValues[ctr]);
    }
}

// The example displays the following output:
//       Position en-US           sv-SE
//
//       0        able            able
//       1        Æble            Æble
//       2        ångström        apple
//       3        apple           Windows
//       4        Visual Studio   Visual Studio
//       5        Windows         ångström
Imports System.Globalization
Imports System.Threading

Module Example18
    Public Sub Main18()
        Dim values() As String = {"able", "ångström", "apple",
                                   "Æble", "Windows", "Visual Studio"}
        ' Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        ' Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values)
        Dim enValues() As String = CType(values.Clone(), String())

        ' Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values)
        Dim svValues() As String = CType(values.Clone(), String())

        ' Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}", "Position", "en-US", "sv-SE")
        Console.WriteLine()
        For ctr As Integer = 0 To values.GetUpperBound(0)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues(ctr), svValues(ctr))
        Next
    End Sub
End Module
' The example displays the following output:
'       Position en-US           sv-SE
'       
'       0        able            able
'       1        Æble            Æble
'       2        ångström        apple
'       3        apple           Windows
'       4        Visual Studio   Visual Studio
'       5        Windows         ångström

Il confronto di stringhe dipendenti dalle impostazioni cultura viene definito dall'oggetto CompareInfo, restituito dalla proprietà CultureInfo.CompareInfo delle singole impostazioni cultura. I confronti di stringhe dipendenti dalle impostazioni cultura che usano gli overload del metodo String.Compare usano anche l'oggetto CompareInfo.

.NET usa le tabelle per eseguire ordinamenti dipendenti dalle impostazioni cultura sui dati di tipo stringa. Il contenuto delle tabelle, che includono dati sui fattori di ordinamento e sulla normalizzazione delle stringhe, è determinato dalla versione dello standard Unicode implementata da una determinata versione di .NET. Nella tabella riportata di seguito sono elencate le versioni di Unicode implementate dalle versioni specifiche di .NET. Questo elenco delle versioni supportate da Unicode si applica soltanto al confronto dei caratteri e all'ordinamento alfabetico. Non si applica alla classificazione di caratteri Unicode in base alla categoria. Per altre informazioni, vedere la sezione "Stringhe e standard Unicode" nell'articolo String.

Versione di .NET Framework Sistema operativo Versione Unicode
.NET Framework 2.0 Tutti i sistemi operativi Unicode 4.1
.NET Framework 3.0 Tutti i sistemi operativi Unicode 4.1
.NET Framework 3.5 Tutti i sistemi operativi Unicode 4.1
.NET Framework 4 Tutti i sistemi operativi Unicode 5.0
.NET Framework 4.5 e versioni successive Windows 7 Unicode 5.0
.NET Framework 4.5 e versioni successive Sistemi operativi Windows 8 e versioni successive Unicode 6.3.0
.NET Core e .NET 5+ Dipende dalla versione dello standard Unicode supportato dal sistema operativo sottostante.

A partire da .NET Framework 4.5 e in tutte le versioni di .NET Core e .NET 5+, il confronto tra stringhe e l'ordinamento variano a seconda del sistema operativo. .NET Framework 4.5 e versioni successive in esecuzione in Windows 7 recupera i dati dalle proprie tabelle che implementano il formato Unicode 5.0. .NET Framework 4.5 e versioni successive in esecuzione in Windows 8 e versioni successive recupera i dati dalle tabelle del sistema operativo che implementano il formato Unicode 6.3. In .NET Core e .NET 5+ la versione di Unicode supportata dipende dal sistema operativo sottostante. Se si serializzano i dati ordinati dipendenti dalle impostazioni cultura, è possibile usare la classe SortVersion per determinare se i dati serializzati devono essere ordinati in modo che siano coerenti con .NET e con l'ordinamento del sistema operativo. Per un esempio, vedere l'argomento relativo alla classe SortVersion.

Se l'app esegue ordinamenti estesi specifici delle impostazioni cultura di dati stringa, è possibile usare la classe SortKey per confrontare le stringhe. Una chiave di ordinamento riflette i fattori di ordinamento specifici alle impostazioni cultura, inclusi i caratteri alfabetici, i caratteri maiuscoli e minuscoli e i segni diacritici di una determinata stringa. Poiché i confronti che usano le chiavi di ordinamento sono binari, risultano più veloci dei confronti che usano un oggetto CompareInfo in modo implicito o esplicito. Si deve creare una chiave di ordinamento specifico delle impostazioni cultura per una determinata stringa passando la stringa al metodo CompareInfo.GetSortKey.

L'esempio seguente è simile all'esempio precedente. Tuttavia, anziché chiamare il metodo Array.Sort(Array), che chiama in modo implicito il metodo CompareInfo.Compare, viene definita un'implementazione System.Collections.Generic.IComparer<T> che confronta le chiavi di ordinamento di cui viene creata un'istanza e vengono passate al metodo Array.Sort<T>(T[], IComparer<T>).

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;

public class SortKeyComparer : IComparer<String>
{
    public int Compare(string? str1, string? str2)
    {
        return (str1, str2) switch
        {
            (null, null) => 0,
            (null, _) => -1,
            (_, null) => 1,
            (var s1, var s2) => SortKey.Compare(
                CultureInfo.CurrentCulture.CompareInfo.GetSortKey(s1),
                CultureInfo.CurrentCulture.CompareInfo.GetSortKey(s1))
        };
    }
}

public class Example19
{
    public static void Main19()
    {
        string[] values = { "able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio" };
        SortKeyComparer comparer = new SortKeyComparer();

        // Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        // Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values, comparer);
        string[] enValues = (String[])values.Clone();

        // Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("sv-SE");
        Array.Sort(values, comparer);
        string[] svValues = (String[])values.Clone();

        // Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}\n", "Position", "en-US", "sv-SE");
        for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues[ctr], svValues[ctr]);
    }
}

// The example displays the following output:
//       Position en-US           sv-SE
//
//       0        able            able
//       1        Æble            Æble
//       2        ångström        apple
//       3        apple           Windows
//       4        Visual Studio   Visual Studio
//       5        Windows         ångström
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Threading

Public Class SortKeyComparer : Implements IComparer(Of String)
    Public Function Compare(str1 As String, str2 As String) As Integer _
           Implements IComparer(Of String).Compare
        Dim sk1, sk2 As SortKey
        sk1 = CultureInfo.CurrentCulture.CompareInfo.GetSortKey(str1)
        sk2 = CultureInfo.CurrentCulture.CompareInfo.GetSortKey(str2)
        Return SortKey.Compare(sk1, sk2)
    End Function
End Class

Module Example19
    Public Sub Main19()
        Dim values() As String = {"able", "ångström", "apple",
                                   "Æble", "Windows", "Visual Studio"}
        Dim comparer As New SortKeyComparer()

        ' Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        ' Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values, comparer)
        Dim enValues() As String = CType(values.Clone(), String())

        ' Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values, comparer)
        Dim svValues() As String = CType(values.Clone(), String())

        ' Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}", "Position", "en-US", "sv-SE")
        Console.WriteLine()
        For ctr As Integer = 0 To values.GetUpperBound(0)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues(ctr), svValues(ctr))
        Next
    End Sub
End Module
' The example displays the following output:
'       Position en-US           sv-SE
'       
'       0        able            able
'       1        Æble            Æble
'       2        ångström        apple
'       3        apple           Windows
'       4        Visual Studio   Visual Studio
'       5        Windows         ångström

Evitare la concatenazione di stringhe

Se possibile, evitare l'uso di stringhe composite che vengono compilate in fase di esecuzione da frasi concatenate. Le stringhe composite sono difficili da localizzare, in quanto richiedono spesso un ordine grammaticale nel linguaggio originale dell'app che non è applicabile ad altre lingue localizzate.

Gestire date e ore

La modalità di gestione dei valori di data e ora dipende se vengono visualizzati nell'interfaccia utente o resi persistenti. In questa sezione vengono esaminati entrambi gli usi. Viene inoltre descritto come gestire le differenze di fuso orario e le operazioni aritmetiche quando si usano date e ore.

Visualizzare date e ore

In genere, quando le date e le ore sono visualizzate nell'interfaccia utente, è consigliabile usare le convenzioni di formattazione delle impostazioni cultura dell'utente, definite dalla proprietà CultureInfo.CurrentCulture e dall'oggetto DateTimeFormatInfo restituito dalla proprietà CultureInfo.CurrentCulture.DateTimeFormat. Le convenzioni di formattazione delle impostazioni cultura correnti vengono automaticamente usate quando si formatta una data tramite uno di questi metodi:

Nell'esempio seguente vengono visualizzati due volte i dati di tramonto e di alba dell'11 ottobre 2012. Le impostazioni cultura correnti vengono impostate prima di tutto su Croato (Croazia) e, successivamente, su Inglese (Regno Unito). In entrambi i casi, le date e le ore verranno visualizzate nel formato appropriato per le impostazioni cultura specifiche.

using System;
using System.Globalization;
using System.Threading;

public class Example3
{
    static DateTime[] dates = { new DateTime(2012, 10, 11, 7, 06, 0),
                        new DateTime(2012, 10, 11, 18, 19, 0) };

    public static void Main3()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hr-HR");
        ShowDayInfo();
        Console.WriteLine();
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        ShowDayInfo();
    }

    private static void ShowDayInfo()
    {
        Console.WriteLine("Date: {0:D}", dates[0]);
        Console.WriteLine("   Sunrise: {0:T}", dates[0]);
        Console.WriteLine("   Sunset:  {0:T}", dates[1]);
    }
}

// The example displays the following output:
//       Date: 11. listopada 2012.
//          Sunrise: 7:06:00
//          Sunset:  18:19:00
//
//       Date: 11 October 2012
//          Sunrise: 07:06:00
//          Sunset:  18:19:00
Imports System.Globalization
Imports System.Threading

Module Example3
    Dim dates() As Date = {New Date(2012, 10, 11, 7, 6, 0),
                            New Date(2012, 10, 11, 18, 19, 0)}

    Public Sub Main3()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hr-HR")
        ShowDayInfo()
        Console.WriteLine()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        ShowDayInfo()
    End Sub

    Private Sub ShowDayInfo()
        Console.WriteLine("Date: {0:D}", dates(0))
        Console.WriteLine("   Sunrise: {0:T}", dates(0))
        Console.WriteLine("   Sunset:  {0:T}", dates(1))
    End Sub
End Module
' The example displays the following output:
'       Date: 11. listopada 2012.
'          Sunrise: 7:06:00
'          Sunset:  18:19:00
'       
'       Date: 11 October 2012
'          Sunrise: 07:06:00
'          Sunset:  18:19:00

Salvare in modo permanente date e ore

È consigliabile non salvare in modo permanente i valori di data e ora in un formato che può variare in base alle impostazioni cultura. Si tratta di un errore di programmazione comune che restituisce dati danneggiati o eccezione di runtime. L'esempio seguente serializza due date, 9 gennaio 2013 e 18 agosto 2013, come stringhe usando le convenzioni di formattazione delle impostazioni cultura inglese (Stati Uniti). I dati recuperati e analizzati mediante le convenzioni delle impostazioni cultura inglese (Stati Uniti) vengono ripristinati correttamente. Tuttavia, quando viene recuperata e analizzata tramite le convenzioni delle impostazioni cultura inglese (Regno Unito), la prima data viene interpretata in modo errato come 1° settembre e la seconda non viene analizzata perché il calendario gregoriano non dispone di un diciottesimo mese.

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

public class Example4
{
    public static void Main4()
    {
        // Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        DateTime[] dates = { new DateTime(2013, 1, 9),
                           new DateTime(2013, 8, 18) };
        StreamWriter sw = new StreamWriter("dateData.dat");
        sw.Write("{0:d}|{1:d}", dates[0], dates[1]);
        sw.Close();

        // Read the persisted data.
        StreamReader sr = new StreamReader("dateData.dat");
        string dateData = sr.ReadToEnd();
        sr.Close();
        string[] dateStrings = dateData.Split('|');

        // Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine("The date is {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr);
        }
        Console.WriteLine();

        // Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine("The date is {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr);
        }
    }
}

// The example displays the following output:
//       Current Culture: English (United States)
//       The date is Wednesday, January 09, 2013
//       The date is Sunday, August 18, 2013
//
//       Current Culture: English (United Kingdom)
//       The date is 01 September 2013
//       ERROR: Unable to parse 8/18/2013
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example4
    Public Sub Main4()
        ' Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim dates() As DateTime = {New DateTime(2013, 1, 9),
                                    New DateTime(2013, 8, 18)}
        Dim sw As New StreamWriter("dateData.dat")
        sw.Write("{0:d}|{1:d}", dates(0), dates(1))
        sw.Close()

        ' Read the persisted data.
        Dim sr As New StreamReader("dateData.dat")
        Dim dateData As String = sr.ReadToEnd()
        sr.Close()
        Dim dateStrings() As String = dateData.Split("|"c)

        ' Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
        Console.WriteLine()

        ' Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       Current Culture: English (United States)
'       The date is Wednesday, January 09, 2013
'       The date is Sunday, August 18, 2013
'       
'       Current Culture: English (United Kingdom)
'       The date is 01 September 2013
'       ERROR: Unable to parse 8/18/2013

È possibile evitare questo problema in tre modi:

  • Serializzare la data e l'ora in formato binario anziché come stringa.
  • Salvare e analizzare la rappresentazione della stringa di data e ora usando una stringa di formato personalizzato uguale, indipendentemente dalle impostazioni cultura dell'utente.
  • Salvare la stringa usando le convenzioni di formattazione della cultura invariabile.

L'ultimo approccio viene illustrato nell'esempio seguente. Vengono usate le convenzioni di formattazione della cultura invariabile restituita dalla proprietà statica CultureInfo.InvariantCulture.

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

public class Example5
{
    public static void Main5()
    {
        // Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        DateTime[] dates = { new DateTime(2013, 1, 9),
                           new DateTime(2013, 8, 18) };
        StreamWriter sw = new StreamWriter("dateData.dat");
        sw.Write(String.Format(CultureInfo.InvariantCulture,
                               "{0:d}|{1:d}", dates[0], dates[1]));
        sw.Close();

        // Read the persisted data.
        StreamReader sr = new StreamReader("dateData.dat");
        string dateData = sr.ReadToEnd();
        sr.Close();
        string[] dateStrings = dateData.Split('|');

        // Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
                                  DateTimeStyles.None, out restoredDate))
                Console.WriteLine("The date is {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr);
        }
        Console.WriteLine();

        // Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
                                  DateTimeStyles.None, out restoredDate))
                Console.WriteLine("The date is {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr);
        }
    }
}

// The example displays the following output:
//       Current Culture: English (United States)
//       The date is Wednesday, January 09, 2013
//       The date is Sunday, August 18, 2013
//
//       Current Culture: English (United Kingdom)
//       The date is 09 January 2013
//       The date is 18 August 2013
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example5
    Public Sub Main5()
        ' Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim dates() As DateTime = {New DateTime(2013, 1, 9),
                                    New DateTime(2013, 8, 18)}
        Dim sw As New StreamWriter("dateData.dat")
        sw.Write(String.Format(CultureInfo.InvariantCulture,
                               "{0:d}|{1:d}", dates(0), dates(1)))
        sw.Close()

        ' Read the persisted data.
        Dim sr As New StreamReader("dateData.dat")
        Dim dateData As String = sr.ReadToEnd()
        sr.Close()
        Dim dateStrings() As String = dateData.Split("|"c)

        ' Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
                             DateTimeStyles.None, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
        Console.WriteLine()

        ' Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
                             DateTimeStyles.None, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       Current Culture: English (United States)
'       The date is Wednesday, January 09, 2013
'       The date is Sunday, August 18, 2013
'       
'       Current Culture: English (United Kingdom)
'       The date is 09 January 2013
'       The date is 18 August 2013

Presenza di fuso orario e serializzazione

Un valore di data e ora può avere più interpretazioni, a partire da un'ora generale (Gli archivi vengono aperti il 2 gennaio 2013 alle 9:00) a un momento specifico ("Data di nascita: 2 gennaio 2013 6.32.00"). Quando un valore di ora rappresenta un momento specifico e si ripristina da un valore serializzato, è necessario assicurarsi che rappresenti lo stesso momento indipendentemente dalla posizione geografica o dal fuso orario dell'utente.

L'esempio seguente illustra questo problema. Un singolo valore di data e ora locale viene salvato come stringa in tre formati standard:

  • "G" per la data estesa generale.
  • "s" per data/ora ordinabili.
  • "o" per la data/ora round trip.
using System;
using System.IO;

public class Example6
{
    public static void Main6()
    {
        DateTime dateOriginal = new DateTime(2023, 3, 30, 18, 0, 0);
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local);

        // Serialize a date.
        if (!File.Exists("DateInfo.dat"))
        {
            StreamWriter sw = new StreamWriter("DateInfo.dat");
            sw.Write("{0:G}|{0:s}|{0:o}", dateOriginal);
            sw.Close();
            Console.WriteLine("Serialized dates to DateInfo.dat");
        }
        Console.WriteLine();

        // Restore the date from string values.
        StreamReader sr = new StreamReader("DateInfo.dat");
        string datesToSplit = sr.ReadToEnd();
        string[] dateStrings = datesToSplit.Split('|');
        foreach (var dateStr in dateStrings)
        {
            DateTime newDate = DateTime.Parse(dateStr);
            Console.WriteLine("'{0}' --> {1} {2}",
                              dateStr, newDate, newDate.Kind);
        }
    }
}
Imports System.IO

Module Example6
    Public Sub Main6()
        ' Serialize a date.
        Dim dateOriginal As Date = #03/30/2023 6:00PM#
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local)
        ' Serialize the date in string form.
        If Not File.Exists("DateInfo.dat") Then
            Dim sw As New StreamWriter("DateInfo.dat")
            sw.Write("{0:G}|{0:s}|{0:o}", dateOriginal)
            sw.Close()
        End If

        ' Restore the date from string values.
        Dim sr As New StreamReader("DateInfo.dat")
        Dim datesToSplit As String = sr.ReadToEnd()
        Dim dateStrings() As String = datesToSplit.Split("|"c)
        For Each dateStr In dateStrings
            Dim newDate As DateTime = DateTime.Parse(dateStr)
            Console.WriteLine("'{0}' --> {1} {2}",
                              dateStr, newDate, newDate.Kind)
        Next
    End Sub
End Module

Quando i dati vengono ripristinati in un sistema con lo stesso fuso orario del sistema su cui sono stati serializzati, i valori di data e ora deserializzati riflettono accuratamente il valore originale, come mostra l'output:

'3/30/2013 6:00:00 PM' --> 3/30/2013 6:00:00 PM Unspecified
'2013-03-30T18:00:00' --> 3/30/2013 6:00:00 PM Unspecified
'2013-03-30T18:00:00.0000000-07:00' --> 3/30/2013 6:00:00 PM Local

Tuttavia, se si ripristinano i dati in un sistema con un fuso orario diverso, solo il valore di data e orario formattato con la stringa di formato standard "O" (roundtrip) conserva le informazioni sul fuso orario e pertanto rappresenta lo stesso istante di tempo. Di seguito è riportato l'output quando i dati relativi a data e ora vengono ripristinati in un sistema con fuso orario standard dell'Europa occidentale:

'3/30/2023 6:00:00 PM' --> 3/30/2023 6:00:00 PM Unspecified
'2023-03-30T18:00:00' --> 3/30/2023 6:00:00 PM Unspecified
'2023-03-30T18:00:00.0000000-07:00' --> 3/31/2023 3:00:00 AM Local

Per riflettere in modo preciso un valore di data e ora che rappresenta un singolo momento di tempo indipendentemente dal fuso orario del sistema in cui i dati sono deserializzati, è possibile eseguire una delle seguenti operazioni:

  • Salvare il valore come stringa usando la stringa di formato standard (round trip) "O". Quindi deserializzarlo nel sistema di destinazione.
  • Convertirlo in UTC e salvarlo come stringa usando la stringa di formato standard "R" (RFC1123). Quindi deserializzarlo nel sistema di destinazione e convertirlo in un'ora locale.
  • Convertirlo in UTC e salvarlo come stringa usando la stringa di formato standard "U" (ordinabile universale). Quindi deserializzarlo nel sistema di destinazione e convertirlo in un'ora locale.

Ogni tecnica viene illustrata nell'esempio riportato di seguito.

using System;
using System.IO;

public class Example9
{
    public static void Main9()
    {
        // Serialize a date.
        DateTime dateOriginal = new DateTime(2023, 3, 30, 18, 0, 0);
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local);

        // Serialize the date in string form.
        if (!File.Exists("DateInfo2.dat"))
        {
            StreamWriter sw = new StreamWriter("DateInfo2.dat");
            sw.Write("{0:o}|{1:r}|{1:u}", dateOriginal,
                                          dateOriginal.ToUniversalTime());
            sw.Close();
        }

        // Restore the date from string values.
        StreamReader sr = new StreamReader("DateInfo2.dat");
        string datesToSplit = sr.ReadToEnd();
        string[] dateStrings = datesToSplit.Split('|');
        for (int ctr = 0; ctr < dateStrings.Length; ctr++)
        {
            DateTime newDate = DateTime.Parse(dateStrings[ctr]);
            if (ctr == 1)
            {
                Console.WriteLine($"'{dateStrings[ctr]}' --> {newDate} {newDate.Kind}");
            }
            else
            {
                DateTime newLocalDate = newDate.ToLocalTime();
                Console.WriteLine($"'{dateStrings[ctr]}' --> {newLocalDate} {newLocalDate.Kind}");
            }
        }
    }
}
Imports System.IO

Module Example9
    Public Sub Main9()
        ' Serialize a date.
        Dim dateOriginal As Date = #03/30/2023 6:00PM#
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local)

        ' Serialize the date in string form.
        If Not File.Exists("DateInfo2.dat") Then
            Dim sw As New StreamWriter("DateInfo2.dat")
            sw.Write("{0:o}|{1:r}|{1:u}", dateOriginal,
                                          dateOriginal.ToUniversalTime())
            sw.Close()
        End If

        ' Restore the date from string values.
        Dim sr As New StreamReader("DateInfo2.dat")
        Dim datesToSplit As String = sr.ReadToEnd()
        Dim dateStrings() As String = datesToSplit.Split("|"c)
        For ctr As Integer = 0 To dateStrings.Length - 1
            Dim newDate As DateTime = DateTime.Parse(dateStrings(ctr))
            If ctr = 1 Then
                Console.WriteLine("'{0}' --> {1} {2}",
                                  dateStrings(ctr), newDate, newDate.Kind)
            Else
                Dim newLocalDate As DateTime = newDate.ToLocalTime()
                Console.WriteLine("'{0}' --> {1} {2}",
                                  dateStrings(ctr), newLocalDate, newLocalDate.Kind)
            End If
        Next
    End Sub
End Module

Quando i dati vengono serializzati in un sistema nel fuso orario del Pacifico e deserializzati in un sistema nel fuso orario dell'Europa occidentale, l'esempio visualizza il seguente output:

'2023-03-30T18:00:00.0000000-07:00' --> 3/31/2023 3:00:00 AM Local
'Sun, 31 Mar 2023 01:00:00 GMT' --> 3/31/2023 3:00:00 AM Local
'2023-03-31 01:00:00Z' --> 3/31/2023 3:00:00 AM Local

Per altre informazioni, vedere Convertire gli orari tra fusi orari.

Eseguire operazioni aritmetiche con data e ora

Entrambi i tipi DateTime e DateTimeOffset supportano le operazioni aritmetiche. È possibile calcolare la differenza tra due valori di data, oppure aggiungere o sottrarre intervalli di tempo particolari a o da un valore di data. Tuttavia, nelle operazioni aritmetiche sui valori di data e ora non vengono presi in considerazione i fusi orari e le relative norme di regolazione. Per questo motivo, le operazioni aritmetiche con date e ore sui valori che rappresentano determinati momenti possono restituire risultati imprecisi.

Ad esempio, la transizione dall'ora solare Pacifico all'ora legale Pacifico si verifica la seconda domenica di marzo, cioè il 10 marzo per l'anno 2013. Come illustrato nell'esempio seguente, se si calcola la data e l'ora corrispondente a 48 ore dopo le 10.30 del 9 marzo 2013 in un sistema con ora solare Pacifico (Stati Uniti), il risultato, 10.30 dell'11 marzo 2013, non tiene conto della regolazione dell'ora.

using System;

public class Example7
{
    public static void Main7()
    {
        DateTime date1 = DateTime.SpecifyKind(new DateTime(2013, 3, 9, 10, 30, 0),
                                              DateTimeKind.Local);
        TimeSpan interval = new TimeSpan(48, 0, 0);
        DateTime date2 = date1 + interval;
        Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2);
    }
}

// The example displays the following output:
//        3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 10:30 AM
Module Example7
    Public Sub Main7()
        Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
                                                 DateTimeKind.Local)
        Dim interval As New TimeSpan(48, 0, 0)
        Dim date2 As Date = date1 + interval
        Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2)
    End Sub
End Module
' The example displays the following output:
'       3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 10:30 AM

Per assicurarsi che un'operazione aritmetica sui valori di data e ora produca risultati accurati, attenersi alla seguente procedura:

  1. Convertire l'ora nel fuso orario di origine in ora UTC.
  2. Eseguire l'operazione aritmetica.
  3. Se il risultato è un valore di data e ora, convertirlo dall'ora UTC all'ora del fuso orario di origine.

L'esempio seguente è simile a quello precedente, con la differenza che segue questi tre passaggi correttamente per aggiungere 48 ore alle 10.30 del 9 marzo 2013.

using System;

public class Example8
{
    public static void Main8()
    {
        TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
        DateTime date1 = DateTime.SpecifyKind(new DateTime(2013, 3, 9, 10, 30, 0),
                                              DateTimeKind.Local);
        DateTime utc1 = date1.ToUniversalTime();
        TimeSpan interval = new TimeSpan(48, 0, 0);
        DateTime utc2 = utc1 + interval;
        DateTime date2 = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst);
        Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2);
    }
}

// The example displays the following output:
//        3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM
Module Example8
    Public Sub Main8()
        Dim pst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")
        Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
                                                 DateTimeKind.Local)
        Dim utc1 As Date = date1.ToUniversalTime()
        Dim interval As New TimeSpan(48, 0, 0)
        Dim utc2 As Date = utc1 + interval
        Dim date2 As Date = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst)
        Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2)
    End Sub
End Module
' The example displays the following output:
'       3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM

Per altre informazioni, vedere Eseguire operazioni aritmetiche con date e ore.

Usare nomi dipendenti delle impostazioni cultura per gli elementi di dati

È possibile che l'app debba visualizzare il nome del mese o il giorno della settimana. A tale scopo, il codice come il seguente è comune.

using System;

public class Example12
{
   public static void Main12()
   {
      DateTime midYear = new DateTime(2013, 7, 1);
      Console.WriteLine("{0:d} is a {1}.", midYear, GetDayName(midYear));
   }

   private static string GetDayName(DateTime date)
   {
      return date.DayOfWeek.ToString("G");
   }
}

// The example displays the following output:
//        7/1/2013 is a Monday.
Module Example12
    Public Sub Main12()
        Dim midYear As Date = #07/01/2013#
        Console.WriteLine("{0:d} is a {1}.", midYear, GetDayName(midYear))
    End Sub

    Private Function GetDayName(dat As Date) As String
        Return dat.DayOfWeek.ToString("G")
    End Function
End Module
' The example displays the following output:
'       7/1/2013 is a Monday.

Tuttavia, tramite il codice vengono restituiti sempre i nomi dei giorni della settimana in inglese. Il codice che estrae il nome del mese è spesso più flessibile. Spesso si presuppone un calendario di dodici mesi con i nomi dei mesi in una lingua specifica.

Usando le stringhe di formato di data e ora personalizzate o le proprietà dell'oggetto DateTimeFormatInfo, è facile estrarre le stringhe che riflettono i nomi dei giorni della settimana o dei mesi nelle impostazioni cultura dell'utente, come illustrato nell'esempio seguente. Vengono impostate le impostazioni cultura correnti su Francese (Francia) e vengono visualizzati il nome del giorno della settimana e il nome del mese per il 1° luglio 2013.

using System;
using System.Globalization;

public class Example13
{
    public static void Main13()
    {
        // Set the current culture to French (France).
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");

        DateTime midYear = new DateTime(2013, 7, 1);
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear));
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName((int)midYear.DayOfWeek));
        Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear));
        Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear.Month));
    }
}

public static class DateUtilities
{
    public static string GetDayName(int dayOfWeek)
    {
        if (dayOfWeek < 0 | dayOfWeek > DateTimeFormatInfo.CurrentInfo.DayNames.Length)
            return String.Empty;
        else
            return DateTimeFormatInfo.CurrentInfo.DayNames[dayOfWeek];
    }

    public static string GetDayName(DateTime date)
    {
        return date.ToString("dddd");
    }

    public static string GetMonthName(int month)
    {
        if (month < 1 | month > DateTimeFormatInfo.CurrentInfo.MonthNames.Length - 1)
            return String.Empty;
        else
            return DateTimeFormatInfo.CurrentInfo.MonthNames[month - 1];
    }

    public static string GetMonthName(DateTime date)
    {
        return date.ToString("MMMM");
    }
}

// The example displays the following output:
//       01/07/2013 is a lundi.
//       01/07/2013 is a lundi.
//       01/07/2013 is in juillet.
//       01/07/2013 is in juillet.
Imports System.Globalization
Imports System.Threading

Module Example13
    Public Sub Main13()
        ' Set the current culture to French (France).
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")

        Dim midYear As Date = #07/01/2013#
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear))
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear.DayOfWeek))
        Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear))
        Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear.Month))
    End Sub
End Module

Public Class DateUtilities
    Public Shared Function GetDayName(dayOfWeek As Integer) As String
        If dayOfWeek < 0 Or dayOfWeek > DateTimeFormatInfo.CurrentInfo.DayNames.Length Then
            Return String.Empty
        Else
            Return DateTimeFormatInfo.CurrentInfo.DayNames(dayOfWeek)
        End If
    End Function

    Public Shared Function GetDayName(dat As Date) As String
        Return dat.ToString("dddd")
    End Function

    Public Shared Function GetMonthName(month As Integer) As String
        If month < 1 Or month > DateTimeFormatInfo.CurrentInfo.MonthNames.Length - 1 Then
            Return String.Empty
        Else
            Return DateTimeFormatInfo.CurrentInfo.MonthNames(month - 1)
        End If
    End Function

    Public Shared Function GetMonthName(dat As Date) As String
        Return dat.ToString("MMMM")
    End Function
End Class
' The example displays the following output:
'       01/07/2013 is a lundi.
'       01/07/2013 is a lundi.
'       01/07/2013 is in juillet.
'       01/07/2013 is in juillet.

Valori numerici

La gestione di numeri dipende se vengono visualizzati nell'interfaccia utente o resi persistenti. In questa sezione vengono esaminati entrambi gli usi.

Nota

Nelle operazioni di analisi e formattazione .NET riconosce come cifre numeriche solo i caratteri latini di base da 0 a 9 (da U+0030 a U+0039).

Visualizzare i valori numerici

In genere, quando i numeri sono visualizzati nell'interfaccia utente, è consigliabile usare le convenzioni di formattazione delle impostazioni cultura dell'utente, definite nella proprietà CultureInfo.CurrentCulture e dall'oggetto NumberFormatInfo restituito dalla proprietà CultureInfo.CurrentCulture.NumberFormat. Le convenzioni di formattazione delle impostazioni cultura correnti vengono usate automaticamente quando si formatta una data nei modi seguenti:

  • Usando il metodo ToString senza parametri di qualsiasi tipo numerico.
  • Usando il metodo ToString(String) di qualsiasi tipo numerico, che include una stringa di formato come argomento.
  • Usando la formattazione composita con valori numerici.

L'esempio seguente consente di visualizzare la temperatura mensile media di Parigi, Francia. Innanzitutto vengono impostate le impostazioni cultura correnti su Francese (Francia) prima di visualizzare i dati e, successivamente, vengono impostate su Inglese (Stati Uniti). In ogni caso, le temperature e i nomi dei mesi verranno visualizzati nel formato appropriato per le impostazioni cultura in questione. Si noti che nelle due impostazioni cultura vengono usati separatori decimali diversi per il valore della temperatura. Si noti inoltre che in questo esempio viene usata la stringa di formato di data e ora personalizzata "MMMM" per visualizzare il nome completo dei mesi e che viene allocata la quantità di spazio appropriata per il nome del mese nella stringa di risultato determinando la lunghezza del nome del mese più lungo nella matrice DateTimeFormatInfo.MonthNames.

using System;
using System.Globalization;
using System.Threading;

public class Example14
{
    public static void Main14()
    {
        DateTime dateForMonth = new DateTime(2013, 1, 1);
        double[] temperatures = {  3.4, 3.5, 7.6, 10.4, 14.5, 17.2,
                                19.9, 18.2, 15.9, 11.3, 6.9, 5.3 };

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName);
        // Build the format string dynamically so we allocate enough space for the month name.
        string fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}";
        for (int ctr = 0; ctr < temperatures.Length; ctr++)
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures[ctr]);

        Console.WriteLine();

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName);
        fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}";
        for (int ctr = 0; ctr < temperatures.Length; ctr++)
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures[ctr]);
    }

    private static int GetLongestMonthNameLength()
    {
        int length = 0;
        foreach (var nameOfMonth in DateTimeFormatInfo.CurrentInfo.MonthNames)
            if (nameOfMonth.Length > length) length = nameOfMonth.Length;

        return length;
    }
}

// The example displays the following output:
//    Current Culture: French (France)
//       janvier        3,4
//       février        3,5
//       mars           7,6
//       avril         10,4
//       mai           14,5
//       juin          17,2
//       juillet       19,9
//       août          18,2
//       septembre     15,9
//       octobre       11,3
//       novembre       6,9
//       décembre       5,3
//
//       Current Culture: English (United States)
//       January        3.4
//       February       3.5
//       March          7.6
//       April         10.4
//       May           14.5
//       June          17.2
//       July          19.9
//       August        18.2
//       September     15.9
//       October       11.3
//       November       6.9
//       December       5.3
Imports System.Globalization
Imports System.Threading

Module Example14
    Public Sub Main14()
        Dim dateForMonth As Date = #1/1/2013#
        Dim temperatures() As Double = {3.4, 3.5, 7.6, 10.4, 14.5, 17.2,
                                         19.9, 18.2, 15.9, 11.3, 6.9, 5.3}

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        Dim fmtString As String = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}"
        For ctr = 0 To temperatures.Length - 1
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures(ctr))
        Next
        Console.WriteLine()

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        ' Build the format string dynamically so we allocate enough space for the month name.
        fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}"
        For ctr = 0 To temperatures.Length - 1
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures(ctr))
        Next
    End Sub

    Private Function GetLongestMonthNameLength() As Integer
        Dim length As Integer
        For Each nameOfMonth In DateTimeFormatInfo.CurrentInfo.MonthNames
            If nameOfMonth.Length > length Then length = nameOfMonth.Length
        Next
        Return length
    End Function
End Module
' The example displays the following output:
'       Current Culture: French (France)
'       janvier        3,4
'       février        3,5
'       mars           7,6
'       avril         10,4
'       mai           14,5
'       juin          17,2
'       juillet       19,9
'       août          18,2
'       septembre     15,9
'       octobre       11,3
'       novembre       6,9
'       décembre       5,3
'       
'       Current Culture: English (United States)
'       January        3.4
'       February       3.5
'       March          7.6
'       April         10.4
'       May           14.5
'       June          17.2
'       July          19.9
'       August        18.2
'       September     15.9
'       October       11.3
'       November       6.9
'       December       5.3

Salvare in modo permanente i valori numerici

È consigliabile non salvare in maniera permanente i dati numerici in un formato specifico delle impostazioni cultura. Si tratta di un errore di programmazione comune che restituisce dati danneggiati o eccezione di runtime. L'esempio seguente genera dieci numeri a virgola mobile casuali e li serializza come stringhe usando le convenzioni di formattazione delle impostazioni cultura della lingua inglese (Stati Uniti). I dati recuperati e analizzati mediante le convenzioni delle impostazioni cultura inglese (Stati Uniti) vengono ripristinati correttamente. Tuttavia, quando vengono recuperati e analizzati usando le convenzioni delle impostazioni cultura Francese (Francia), nessuno dei numeri può essere analizzato in quanto vengono usati separatori decimali diversi nelle impostazioni cultura.

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

public class Example15
{
    public static void Main15()
    {
        // Create ten random doubles.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        double[] numbers = GetRandomNumbers(10);
        DisplayRandomNumbers(numbers);

        // Persist the numbers as strings.
        StreamWriter sw = new StreamWriter("randoms.dat");
        for (int ctr = 0; ctr < numbers.Length; ctr++)
            sw.Write("{0:R}{1}", numbers[ctr], ctr < numbers.Length - 1 ? "|" : "");

        sw.Close();

        // Read the persisted data.
        StreamReader sr = new StreamReader("randoms.dat");
        string numericData = sr.ReadToEnd();
        sr.Close();
        string[] numberStrings = numericData.Split('|');

        // Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var numberStr in numberStrings)
        {
            double restoredNumber;
            if (Double.TryParse(numberStr, out restoredNumber))
                Console.WriteLine(restoredNumber.ToString("R"));
            else
                Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr);
        }
        Console.WriteLine();

        // Restore and display the data using the conventions of the fr-FR culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var numberStr in numberStrings)
        {
            double restoredNumber;
            if (Double.TryParse(numberStr, out restoredNumber))
                Console.WriteLine(restoredNumber.ToString("R"));
            else
                Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr);
        }
    }

    private static double[] GetRandomNumbers(int n)
    {
        Random rnd = new Random();
        double[] numbers = new double[n];
        for (int ctr = 0; ctr < n; ctr++)
            numbers[ctr] = rnd.NextDouble() * 1000;
        return numbers;
    }

    private static void DisplayRandomNumbers(double[] numbers)
    {
        for (int ctr = 0; ctr < numbers.Length; ctr++)
            Console.WriteLine(numbers[ctr].ToString("R"));
        Console.WriteLine();
    }
}

// The example displays output like the following:
//       487.0313743534644
//       674.12000879371533
//       498.72077885024288
//       42.3034229512808
//       970.57311049223563
//       531.33717716268131
//       587.82905693530529
//       562.25210175023039
//       600.7711019370571
//       299.46113717717174
//
//       Current Culture: English (United States)
//       487.0313743534644
//       674.12000879371533
//       498.72077885024288
//       42.3034229512808
//       970.57311049223563
//       531.33717716268131
//       587.82905693530529
//       562.25210175023039
//       600.7711019370571
//       299.46113717717174
//
//       Current Culture: French (France)
//       ERROR: Unable to parse '487.0313743534644'
//       ERROR: Unable to parse '674.12000879371533'
//       ERROR: Unable to parse '498.72077885024288'
//       ERROR: Unable to parse '42.3034229512808'
//       ERROR: Unable to parse '970.57311049223563'
//       ERROR: Unable to parse '531.33717716268131'
//       ERROR: Unable to parse '587.82905693530529'
//       ERROR: Unable to parse '562.25210175023039'
//       ERROR: Unable to parse '600.7711019370571'
//       ERROR: Unable to parse '299.46113717717174'
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example15
    Public Sub Main15()
        ' Create ten random doubles.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim numbers() As Double = GetRandomNumbers(10)
        DisplayRandomNumbers(numbers)

        ' Persist the numbers as strings.
        Dim sw As New StreamWriter("randoms.dat")
        For ctr As Integer = 0 To numbers.Length - 1
            sw.Write("{0:R}{1}", numbers(ctr), If(ctr < numbers.Length - 1, "|", ""))
        Next
        sw.Close()

        ' Read the persisted data.
        Dim sr As New StreamReader("randoms.dat")
        Dim numericData As String = sr.ReadToEnd()
        sr.Close()
        Dim numberStrings() As String = numericData.Split("|"c)

        ' Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each numberStr In numberStrings
            Dim restoredNumber As Double
            If Double.TryParse(numberStr, restoredNumber) Then
                Console.WriteLine(restoredNumber.ToString("R"))
            Else
                Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr)
            End If
        Next
        Console.WriteLine()

        ' Restore and display the data using the conventions of the fr-FR culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each numberStr In numberStrings
            Dim restoredNumber As Double
            If Double.TryParse(numberStr, restoredNumber) Then
                Console.WriteLine(restoredNumber.ToString("R"))
            Else
                Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr)
            End If
        Next
    End Sub

    Private Function GetRandomNumbers(n As Integer) As Double()
        Dim rnd As New Random()
        Dim numbers(n - 1) As Double
        For ctr As Integer = 0 To n - 1
            numbers(ctr) = rnd.NextDouble * 1000
        Next
        Return numbers
    End Function

    Private Sub DisplayRandomNumbers(numbers As Double())
        For ctr As Integer = 0 To numbers.Length - 1
            Console.WriteLine(numbers(ctr).ToString("R"))
        Next
        Console.WriteLine()
    End Sub
End Module
' The example displays output like the following:
'       487.0313743534644
'       674.12000879371533
'       498.72077885024288
'       42.3034229512808
'       970.57311049223563
'       531.33717716268131
'       587.82905693530529
'       562.25210175023039
'       600.7711019370571
'       299.46113717717174
'       
'       Current Culture: English (United States)
'       487.0313743534644
'       674.12000879371533
'       498.72077885024288
'       42.3034229512808
'       970.57311049223563
'       531.33717716268131
'       587.82905693530529
'       562.25210175023039
'       600.7711019370571
'       299.46113717717174
'       
'       Current Culture: French (France)
'       ERROR: Unable to parse '487.0313743534644'
'       ERROR: Unable to parse '674.12000879371533'
'       ERROR: Unable to parse '498.72077885024288'
'       ERROR: Unable to parse '42.3034229512808'
'       ERROR: Unable to parse '970.57311049223563'
'       ERROR: Unable to parse '531.33717716268131'
'       ERROR: Unable to parse '587.82905693530529'
'       ERROR: Unable to parse '562.25210175023039'
'       ERROR: Unable to parse '600.7711019370571'
'       ERROR: Unable to parse '299.46113717717174'

Per evitare questo problema, usare una delle seguenti tecniche:

  • Salvare e analizzare la rappresentazione di stringa del numero usando una stringa di formato personalizzata uguale indipendentemente dalle impostazioni cultura dell'utente.
  • Salvare il numero come stringa usando le convenzioni di formattazione della lingua inglese restituita dalla proprietà CultureInfo.InvariantCulture.

La serializzazione dei valori di valuta è un caso speciale. Poiché un valore di valuta dipende dall'unità di valuta in cui viene espresso, non è opportuno considerarlo un valore numerico indipendente. Tuttavia, se si salva un valore di valuta come stringa formattata in cui è incluso un simbolo di valuta, non è possibile deserializzarlo in un sistema in cui le impostazioni cultura predefinite usano un simbolo di valuta diverso, come illustrato nell'esempio di seguito.

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

public class Example1
{
   public static void Main1()
   {
      // Display the currency value.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
      Decimal value = 16039.47m;
      Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName);
      Console.WriteLine("Currency Value: {0:C2}", value);

      // Persist the currency value as a string.
      StreamWriter sw = new StreamWriter("currency.dat");
      sw.Write(value.ToString("C2"));
      sw.Close();

      // Read the persisted data using the current culture.
      StreamReader sr = new StreamReader("currency.dat");
      string currencyData = sr.ReadToEnd();
      sr.Close();

      // Restore and display the data using the conventions of the current culture.
      Decimal restoredValue;
      if (Decimal.TryParse(currencyData, out restoredValue))
         Console.WriteLine(restoredValue.ToString("C2"));
      else
         Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData);
      Console.WriteLine();

      // Restore and display the data using the conventions of the en-GB culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
      Console.WriteLine("Current Culture: {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      if (Decimal.TryParse(currencyData, NumberStyles.Currency, null, out restoredValue))
         Console.WriteLine(restoredValue.ToString("C2"));
      else
         Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData);
      Console.WriteLine();
   }
}
// The example displays output like the following:
//       Current Culture: English (United States)
//       Currency Value: $16,039.47
//       ERROR: Unable to parse '$16,039.47'
//
//       Current Culture: English (United Kingdom)
//       ERROR: Unable to parse '$16,039.47'
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example1
    Public Sub Main1()
        ' Display the currency value.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim value As Decimal = 16039.47D
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        Console.WriteLine("Currency Value: {0:C2}", value)

        ' Persist the currency value as a string.
        Dim sw As New StreamWriter("currency.dat")
        sw.Write(value.ToString("C2"))
        sw.Close()

        ' Read the persisted data using the current culture.
        Dim sr As New StreamReader("currency.dat")
        Dim currencyData As String = sr.ReadToEnd()
        sr.Close()

        ' Restore and display the data using the conventions of the current culture.
        Dim restoredValue As Decimal
        If Decimal.TryParse(currencyData, restoredValue) Then
            Console.WriteLine(restoredValue.ToString("C2"))
        Else
            Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData)
        End If
        Console.WriteLine()

        ' Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        If Decimal.TryParse(currencyData, NumberStyles.Currency, Nothing, restoredValue) Then
            Console.WriteLine(restoredValue.ToString("C2"))
        Else
            Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData)
        End If
        Console.WriteLine()
    End Sub
End Module
' The example displays output like the following:
'       Current Culture: English (United States)
'       Currency Value: $16,039.47
'       ERROR: Unable to parse '$16,039.47'
'       
'       Current Culture: English (United Kingdom)
'       ERROR: Unable to parse '$16,039.47'

In alternativa, serializzare il valore numerico con alcune informazioni relative alle impostazioni cultura, ad esempio il nome delle impostazioni cultura, in modo che il valore e il relativo simbolo di valuta possano essere deserializzati indipendentemente dalle impostazioni cultura correnti. Nell'esempio seguente viene definita una struttura CurrencyValue con due membri: il valore Decimal e il nome delle impostazioni cultura a cui appartiene il valore.

using System;
using System.Globalization;
using System.Text.Json;
using System.Threading;

public class Example2
{
    public static void Main2()
    {
        // Display the currency value.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        Decimal value = 16039.47m;
        Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
        Console.WriteLine($"Currency Value: {value:C2}");

        // Serialize the currency data.
        CurrencyValue data = new()
        {
            Amount = value,
            CultureName = CultureInfo.CurrentCulture.Name
        };
        string serialized = JsonSerializer.Serialize(data);
        Console.WriteLine();

        // Change the current culture.
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");

        // Deserialize the data.
        CurrencyValue restoredData = JsonSerializer.Deserialize<CurrencyValue>(serialized);

        // Display the round-tripped value.
        CultureInfo culture = CultureInfo.CreateSpecificCulture(restoredData.CultureName);
        Console.WriteLine($"Currency Value: {restoredData.Amount.ToString("C2", culture)}");
    }
}

internal struct CurrencyValue
{
    public decimal Amount { get; set; }
    public string CultureName { get; set; }
}

// The example displays the following output:
//       Current Culture: English (United States)
//       Currency Value: $16,039.47
//
//       Current Culture: English (United Kingdom)
//       Currency Value: $16,039.47
Imports System.Globalization
Imports System.Text.Json
Imports System.Threading

Friend Structure CurrencyValue
    Public Property Amount As Decimal
    Public Property CultureName As String
End Structure

Module Example2
    Public Sub Main2()
        ' Display the currency value.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim value As Decimal = 16039.47D
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        Console.WriteLine("Currency Value: {0:C2}", value)

        ' Serialize the currency data.
        Dim data As New CurrencyValue With {
            .Amount = value,
            .CultureName = CultureInfo.CurrentCulture.Name
        }

        Dim serialized As String = JsonSerializer.Serialize(data)
        Console.WriteLine()

        ' Change the current culture.
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)

        ' Deserialize the data.
        Dim restoredData As CurrencyValue = JsonSerializer.Deserialize(Of CurrencyValue)(serialized)

        ' Display the round-tripped value.
        Dim culture As CultureInfo = CultureInfo.CreateSpecificCulture(restoredData.CultureName)
        Console.WriteLine("Currency Value: {0}", restoredData.Amount.ToString("C2", culture))
    End Sub
End Module

' The example displays the following output:
'       Current Culture: English (United States)
'       Currency Value: $16,039.47
'       
'       Current Culture: English (United Kingdom)
'       Currency Value: $16,039.47

Usare aspetti specifici delle impostazioni cultura

In .NET la classe CultureInfo rappresenta impostazioni cultura specifiche o un'area specifica. Alcune proprietà restituiscono oggetti che includono informazioni specifiche su alcuni aspetti delle impostazioni cultura:

In genere, non fare supposizioni sui valori della proprietà CultureInfo specifiche e sui relativi oggetti correlati. Considerare invece i dati specifici delle impostazioni cultura come elementi soggetti a modifiche, per questi motivi:

  • I singoli valori di proprietà sono soggetti a modifiche e revisioni nel tempo, poiché i dati vengono corretti, si rendono disponibili dati migliori o si apportano modifiche di convenzioni specifiche delle impostazioni cultura.

  • I singoli valori di proprietà possono variare tra le diverse versioni di .NET o del sistema operativo.

  • .NET supporta impostazioni cultura sostitutive. Ciò consente di definire nuove impostazioni cultura personalizzate che completano le impostazioni cultura standard esistenti o li sostituiscono completamente.

  • Nei sistemi Windows l'utente può personalizzare aspetti specifici delle impostazioni cultura usando l'app Paese e lingua nel Pannello di controllo. Quando viene creata un'istanza di un oggetto CultureInfo, è possibile determinare se rifletta le personalizzazioni dell'utente chiamando il costruttore CultureInfo(String, Boolean). In genere, per le app degli utenti finali, è necessario rispettare le preferenze dell'utente in modo da offrirgli i dati in un formato che l'utente si aspetta.

Vedi anche