Condividi tramite


Globalizzazione

La globalizzazione implica la progettazione e lo sviluppo di un'applicazione pronta per il mondo che supporta interfacce localizzate e dati regionali per utenti in culture diverse. Prima di iniziare la fase di progettazione, dovresti determinare quali culture l'app supporterà. Anche se un'app è destinata a una singola cultura o area geografica come predefinita, è possibile progettarla e scriverla in modo che possa essere estesa facilmente agli utenti di altre impostazioni cultura o aree geografiche.

In quanto sviluppatori, abbiamo tutti dei presupposti sulle interfacce utente e i dati che sono formati dalle nostre culture. Ad esempio, per uno sviluppatore di lingua inglese negli Stati Uniti, la serializzazione dei dati di data e ora come stringa nel formato MM/dd/yyyy hh:mm:ss sembra perfettamente ragionevole. Tuttavia, è probabile che la deserializzazione di tale stringa in un sistema con impostazioni culturali diverse generi un'eccezione FormatException o produca dati non accurati. La globalizzazione consente di identificare tali presupposti specifici delle impostazioni cultura e assicurarsi che non influiscano sulla progettazione o sul codice dell'app.

Questo articolo illustra alcuni dei problemi principali da considerare e le procedure consigliate che è possibile seguire per la gestione di stringhe, valori di data e ora e valori numerici in un'app globalizzata.

Stringhe

La gestione di caratteri e stringhe è un obiettivo centrale della globalizzazione, perché ogni cultura o area può usare caratteri e set di caratteri diversi e ordinarli in modo diverso. In questa sezione vengono fornite indicazioni per l'uso di stringhe nelle app globalizzate.

Usare Unicode internamente

Per impostazione predefinita, .NET usa stringhe Unicode. Una stringa Unicode è costituita da zero, uno o più Char oggetti, ognuno dei quali rappresenta un'unità di codice UTF-16. Esiste una rappresentazione Unicode per quasi ogni carattere in ogni set di caratteri in uso in tutto il mondo.

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

L'esempio seguente illustra i pericoli di interpretare i dati della tabella codici quando la tabella codici predefinita in un sistema è diversa dalla tabella codici in cui sono stati salvati i dati. Per simulare questo scenario, l'esempio specifica in modo esplicito tabelle codici diverse. In primo luogo, l'esempio definisce una matrice costituita dai caratteri maiuscoli dell'alfabeto greco. Li codifica in una matrice di byte usando la tabella codici 737 (nota anche come MS-DOS greco) e salva la matrice di byte in un file. Se il file viene recuperato e la relativa matrice di byte viene decodificata usando la tabella codici 737, i caratteri originali vengono ripristinati. Tuttavia, se il file viene recuperato e la relativa matrice di byte viene decodificata usando la tabella codici 1252 (o Windows-1252, che rappresenta i caratteri nell'alfabeto latino), i caratteri originali andranno 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 garantisce che le stesse unità di codice eseguano sempre il mapping agli stessi caratteri e che gli stessi caratteri eseguano sempre il mapping alle stesse matrici di byte.

Usare i file di risorse

Anche se si sviluppa un'app destinata a una singola cultura o area geografica, è consigliabile usare i file di risorse per archiviare stringhe e altre risorse visualizzate nell'interfaccia utente. È consigliabile non aggiungerli direttamente al codice. L'uso dei file di risorse presenta diversi vantaggi:

  • Tutte le stringhe si trovano in un'unica posizione. Non è necessario eseguire ricerche in tutto il codice sorgente 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 vengano trascurate quando viene modificata una stringa.
  • È possibile includere risorse non stringa, ad esempio immagini o dati binari, nel file di risorse anziché archiviarle in un file autonomo separato, in modo che possano essere recuperate facilmente.

L'uso dei file di risorse presenta particolari vantaggi se si crea un'app localizzata. Quando si distribuiscono risorse negli assembly satellite, il Common Language Runtime seleziona automaticamente una risorsa appropriata per la cultura dell'utente in base alla cultura corrente dell'interfaccia utente, come definito dalla proprietà CultureInfo.CurrentUICulture. Se si fornisce una risorsa specifica delle impostazioni cultura appropriata e si crea correttamente un'istanza di un ResourceManager oggetto o si usa una classe di risorse fortemente tipizzata, il runtime gestisce i dettagli del 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 satelliti, vedere Creare assembly satelliti e Pacchettizzare e Distribuire risorse.

Ricercare e confrontare stringhe

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

Suggerimento

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

Nelle ricerche e nei confronti di stringhe, un errore comune consiste nel considerare la stringa come una raccolta di caratteri, ognuna delle quali è rappresentata da un Char oggetto . Infatti, un singolo carattere può essere formato da uno, due o più Char oggetti. Tali caratteri si trovano più frequentemente nelle stringhe provenienti da culture i cui alfabeti sono costituiti da caratteri al di fuori dell'intervallo dei caratteri latini di base Unicode (da U+0021 a U+007E). Nell'esempio seguente viene eseguito un tentativo di trovare l'indice della LETTERA MAIUSCOLA LATINA A 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 viene rappresentato nell'istanza di stringa da due Char oggetti, U+0041 e U+0300. Il codice di esempio chiama i sovraccarichi String.IndexOf(Char) e String.IndexOf(String) per trovare la posizione di questo carattere nell'istanza della stringa, ma restituiscono risultati diversi. La prima chiamata al metodo ha un argomento; esegue un Char confronto ordinale e pertanto non riesce a trovare una corrispondenza. La seconda chiamata ha un argomento String; esegue un confronto sensibile alla cultura e quindi 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:   {composite.IndexOf('\u00C0')}");
        Console.WriteLine($"Comparing using String: {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 alcune ambiguità di questo esempio (chiamate a due overload simili di un metodo che restituiscono risultati diversi) chiamando un overload che include un parametro StringComparison, come ad esempio il metodo String.IndexOf(String, StringComparison) o il metodo String.LastIndexOf(String, StringComparison).

Tuttavia, le ricerche non sono sempre sensibili alle differenze culturali. Se lo scopo della ricerca è prendere una decisione di sicurezza o consentire o impedire l'accesso ad alcune risorse, il confronto deve essere ordinale, come illustrato nella sezione successiva.

Verificare l'uguaglianza delle stringhe

Se si desidera testare due stringhe per verificarne l'uguaglianza anziché determinare il modo in cui vengono confrontate nell'ordinamento, usare il String.Equals metodo anziché un metodo di confronto tra stringhe, String.Compare ad esempio o CompareInfo.Compare.

I confronti per l'uguaglianza vengono in genere eseguiti per accedere ad alcune risorse in modo condizionale. Ad esempio, è possibile eseguire un confronto per verificare l'uguaglianza di una password o per verificare che esista un file. Tali confronti non linguistici devono essere sempre ordinali anziché sensibili al contesto culturale. In generale, è necessario chiamare il metodo di istanza String.Equals(String, StringComparison) o il metodo statico String.Equals(String, String, StringComparison) con un valore di StringComparison.Ordinal per stringhe come le password e un valore di StringComparison.OrdinalIgnoreCase per stringhe, ad esempio nomi di file o URI.

I confronti per l'uguaglianza talvolta comportano ricerche o confronti di sottostringhe anziché chiamate al metodo String.Equals. In alcuni casi, è possibile usare una ricerca di sottostringa per determinare se tale sottostringa è uguale a un'altra stringa. Se il fine di questo confronto non è linguistico, la ricerca deve anche essere ordinale anziché sensibile alle impostazioni culturali.

Il seguente esempio illustra il pericolo di una ricerca sensibile alla cultura su dati non linguistici. Il AccessesFileSystem metodo è progettato per impedire l'accesso al file system per gli URI che iniziano con la sottostringa "FILE". A tale scopo, esegue un confronto sensibile alla cultura e insensibile al maiuscolo/minuscolo 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) è sempre l'equivalente minuscolo di "I" (U+0049). Tuttavia, in turco e azero, la versione maiuscola di "i" è "İ" (U+0130). A causa di questa discrepanza, il confronto sensibile alle impostazioni cultura consente l'accesso al file system quando deve essere vietato.

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 la distinzione tra maiuscole e minuscole, come illustrato nell'esempio seguente.

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 e organizzare stringhe

In genere, le stringhe ordinate da visualizzare nell'interfaccia utente devono essere classificate in base alla cultura. Nella maggior parte dei casi, tali confronti di stringhe vengono gestiti in modo implicito da .NET quando si chiama un metodo che ordina le stringhe, ad esempio Array.Sort o List<T>.Sort. Per impostazione predefinita, le stringhe vengono ordinate usando le convenzioni di ordinamento della cultura corrente. Nell'esempio seguente viene illustrata la differenza quando una matrice di stringhe viene ordinata usando le convenzioni culturali inglesi (Stati Uniti) e svedesi (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 tra stringhe sensibili alle impostazioni culturali viene definito dall'oggetto CompareInfo restituito dalla proprietà CultureInfo.CompareInfo di ciascuna cultura. I confronti di stringhe sensibili alla cultura che utilizzano le versioni sovraccaricate del metodo String.Compare usano anche l'oggetto CompareInfo.

.NET usa le tabelle per eseguire ordinamenti sensibili alla cultura sui dati di tipo stringa. Il contenuto di queste tabelle, che contengono dati relativi ai pesi di ordinamento e alla normalizzazione delle stringhe, è determinato dalla versione dello standard Unicode implementata da una determinata versione di .NET. Nella tabella seguente sono elencate le versioni di Unicode implementate dalle versioni specificate di .NET. Questo elenco di versioni Unicode supportate si applica solo al confronto e all'ordinamento dei caratteri; non si applica alla classificazione dei caratteri Unicode per 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 e l'ordinamento delle stringhe dipendono dal sistema operativo. .NET Framework 4.5 e versioni successive in esecuzione in Windows 7 recupera i dati dalle proprie tabelle che implementano 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 Unicode 6.3. In .NET Core e .NET 5+, la versione supportata di Unicode dipende dal sistema operativo sottostante. Se si serializzano dati ordinati sensibili alle impostazioni cultura, è possibile usare la classe SortVersion per determinare quando 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 SortVersion classe.

Se la tua app esegue ordinamenti di dati stringa specifici della cultura, puoi usare la classe SortKey per confrontare le stringhe. Una chiave di ordinamento riflette i pesi di ordinamento specifici delle impostazioni culturali, inclusi i pesi alfabetici, maiuscole e minuscole e diacritici di una determinata stringa. Poiché i confronti che usano chiavi di ordinamento sono binari, sono più veloci rispetto ai confronti che usano un CompareInfo oggetto in modo implicito o esplicito. Per creare una chiave di ordinamento specifica delle impostazioni cultura per una determinata stringa, è necessario passare la stringa al metodo CompareInfo.GetSortKey.

L'esempio seguente è simile all'esempio precedente. Tuttavia, anziché chiamare il metodo Array.Sort(Array), che chiama implicitamente il metodo CompareInfo.Compare, definisce un'implementazione System.Collections.Generic.IComparer<T> che confronta le chiavi di ordinamento, la quale viene istanziata e passata 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 di usare stringhe composte compilate in fase di esecuzione da frasi concatenate. Le stringhe composite sono difficili da localizzare, perché spesso presuppongono un ordine grammaticale nella lingua originale dell'app che non si applica ad altre lingue localizzate.

Gestire date e ore

La modalità di gestione dei valori di data e ora dipende dal fatto che vengano visualizzati nell'interfaccia utente o salvati in modo permanente. In questa sezione vengono esaminati entrambi gli utilizzi. Illustra anche come gestire le differenze del fuso orario e le operazioni aritmetiche quando si lavora con date e ore.

Visualizzare date e ore

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

Nell'esempio seguente vengono visualizzati due volte i dati relativi all'alba e al tramonto per il 11 ottobre 2012. Imposta innanzitutto le impostazioni cultura correnti su Croato (Croazia) e poi su Inglese (Regno Unito). In ogni caso, le date e le ore vengono visualizzate nel formato appropriato per quella cultura.

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: {dates[0]:D}");
        Console.WriteLine($"   Sunrise: {dates[0]:T}");
        Console.WriteLine($"   Sunset:  {dates[1]:T}");
    }
}

// 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

Rendere persistenti date e ore

Non si dovrebbe mai rendere persistenti i dati di data e ora in un formato che può variare in base alla cultura. Si tratta di un errore di programmazione comune che genera dati danneggiati o un'eccezione di runtime. Nell'esempio seguente vengono serializzate due date, il 9 gennaio 2013 e il 18 agosto 2013, come stringhe usando le convenzioni di formattazione delle impostazioni cultura inglese (Stati Uniti). Quando i dati vengono recuperati e analizzati usando le convenzioni della cultura inglese (Stati Uniti), vengono ripristinati correttamente. Tuttavia, quando viene recuperato e analizzato seguendo le convenzioni della cultura inglese (Regno Unito), la prima data viene interpretata erroneamente come 1 settembre e la seconda non può essere analizzata perché il calendario gregoriano non ha 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: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine($"The date is {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {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: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine($"The date is {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {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 uno dei tre modi seguenti:

  • Serializzare la data e l'ora in formato binario anziché come stringa.
  • Salva e analizza la rappresentazione di stringa della data e dell'ora usando una stringa di formato personalizzata identica indipendentemente dalle impostazioni culturali dell'utente.
  • Salva la stringa utilizzando le convenzioni di formattazione della cultura invariante.

Nell'esempio seguente viene illustrato l'ultimo approccio. Usa le convenzioni di formattazione delle impostazioni cultura invarianti restituite 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: {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 {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {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: {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 {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {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

Serializzazione e consapevolezza del fuso orario

Un valore di data e ora può avere più interpretazioni, che vanno da un'ora generale ("I negozi aprono il 2 gennaio 2013, alle 9:00 A.M.") a un momento specifico nel tempo ("Data di nascita: 2 gennaio 2013 6:32:00 A.M."). Quando un valore di ora rappresenta un momento specifico nel tempo e lo si ripristina da un valore serializzato, è necessario assicurarsi che rappresenti lo stesso momento nel tempo indipendentemente dalla posizione geografica o dal fuso orario dell'utente.

Nell'esempio seguente viene illustrato questo problema. Salva un singolo valore di data e ora locale come stringa in tre formati standard:

  • "G" per data e ora generali complete.
  • "s" per data/ora ordinabili.
  • "o" per la data/ora del viaggio di andata e ritorno.
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($"'{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 nello stesso fuso orario del sistema in cui è stato serializzato, i valori di data e ora deserializzati riflettono accuratamente il valore originale, come illustrato nell'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 in un fuso orario diverso, solo il valore di data e ora formattato con la stringa di formato standard "o" (round trip) mantiene le informazioni sul fuso orario e quindi rappresenta lo stesso istante in tempo. Ecco l'output quando i dati di data e ora vengono ripristinati in un sistema nel fuso orario standard romance:

'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 accurato un valore di data e ora che rappresenta un singolo momento di tempo indipendentemente dal fuso orario del sistema in cui i dati vengono deserializzati, è possibile eseguire una delle operazioni seguenti:

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

Nell'esempio seguente viene illustrata ogni tecnica.

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 standard del Pacifico e deserializzati in un sistema nel fuso orario standard romance, nell'esempio viene visualizzato l'output seguente:

'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 di data e ora

Entrambi i DateTime tipi e DateTimeOffset supportano operazioni aritmetiche. È possibile calcolare la differenza tra due valori di data oppure è possibile aggiungere o sottrarre determinati intervalli di tempo a o da un valore di data. Tuttavia, le operazioni aritmetiche sui valori di data e ora non prendono in considerazione i fusi orari e le regole di regolazione del fuso orario. Per questo motivo, l'aritmetica di data e ora sui valori che rappresentano momenti nel tempo può restituire risultati imprecisi.

Ad esempio, la transizione dall'ora solare del Pacifico all'ora legale del Pacifico viene eseguita la seconda domenica di marzo, ovvero il 10 marzo per l'anno 2013. Come illustrato nell'esempio seguente, se si calcola la data e l'ora di 48 ore dopo il 9 marzo 2013 alle 10:30 in un sistema nel fuso orario standard del Pacifico, il risultato, il 11 marzo 2013 alle 10:30, non tiene conto dell'adeguamento del tempo necessario.

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($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
    }
}

// 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 garantire che un'operazione aritmetica sui valori di data e ora produci risultati accurati, seguire questa procedura:

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

L'esempio seguente è simile all'esempio precedente, con la differenza che segue questi tre passaggi per aggiungere correttamente 48 ore al 9 marzo 2013 alle 10:30.

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($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
    }
}

// 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 sensibili alle impostazioni cultura per gli elementi di data

L'app potrebbe dover visualizzare il nome del mese o del 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($"{midYear:d} is a {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, questo codice restituisce sempre i nomi dei giorni della settimana in inglese. Il codice che estrae il nome del mese è spesso ancora più rigido. Spesso presuppone un calendario di dodici mesi con nomi di mesi in una lingua specifica.

Usando stringhe di formato di data e ora personalizzate o le proprietà dell'oggetto DateTimeFormatInfo , è facile estrarre stringhe che riflettono i nomi dei giorni della settimana o dei mesi nelle impostazioni cultura dell'utente, come illustrato nell'esempio seguente. Cambia le impostazioni cultura correnti in francese (Francia) e visualizza 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($"{midYear:d} is a {DateUtilities.GetDayName(midYear)}.");
        Console.WriteLine($"{midYear:d} is a {DateUtilities.GetDayName((int)midYear.DayOfWeek)}.");
        Console.WriteLine($"{midYear:d} is in {DateUtilities.GetMonthName(midYear)}.");
        Console.WriteLine($"{midYear:d} is in {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 dei numeri dipende dal fatto che vengano visualizzati nell'interfaccia utente o salvati in modo permanente. In questa sezione vengono esaminati entrambi gli utilizzi.

Annotazioni

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

Visualizzare i valori numerici

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

  • Utilizzo di un metodo senza parametri ToString di qualsiasi tipo numerico.
  • Utilizzando il ToString(String) metodo di qualsiasi tipo numerico, che include una stringa di formato come argomento.
  • Uso della formattazione composita con valori numerici.

Nell'esempio seguente viene visualizzata la temperatura media al mese a Parigi, Francia. Imposta innanzitutto le impostazioni della cultura corrente su Francese (Francia) prima di visualizzare i dati e poi su Inglese (Stati Uniti). In ogni caso, i nomi dei mesi e le temperature vengono visualizzati nel formato appropriato per tali impostazioni cultura. Si noti che le due impostazioni cultura usano separatori decimali diversi nel valore della temperatura. Si noti inoltre che nell'esempio viene utilizzata la stringa di formato di data e ora personalizzata "MMMM" per visualizzare il nome completo del mese e che alloca la quantità di spazio appropriata per il nome del mese nella stringa di risultato determinando la lunghezza del nome del mese più lungo nella DateTimeFormatInfo.MonthNames matrice.

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: {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: {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

Rendere persistenti i valori numerici

È consigliabile non rendere persistenti i dati numerici in un formato specifico delle impostazioni cultura. Si tratta di un errore di programmazione comune che genera dati danneggiati o un'eccezione di runtime. Nell'esempio seguente vengono generati dieci numeri a virgola mobile casuali e quindi li serializza come stringhe usando le convenzioni di formattazione delle impostazioni cultura inglese (Stati Uniti). Quando i dati vengono recuperati e analizzati usando le convenzioni della cultura inglese (Stati Uniti), vengono ripristinati correttamente. Tuttavia, quando viene recuperato e analizzato usando le convenzioni delle impostazioni cultura francese (Francia), nessuno dei numeri può essere analizzato perché le impostazioni cultura usano separatori decimali diversi.

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: {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 '{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: {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 '{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, è possibile usare una di queste tecniche:

  • Salvare e analizzare la rappresentazione di stringa del numero usando una stringa di formato personalizzata identica indipendentemente dalle impostazioni cultura dell'utente.
  • Salvare il numero come stringa usando le convenzioni di formattazione delle impostazioni cultura invarianti, restituite dalla CultureInfo.InvariantCulture proprietà .

La serializzazione dei valori di valuta è un caso speciale. Poiché un valore di valuta dipende dall'unità di valuta in cui è espressa, ha poco senso considerarlo come un valore numerico indipendente. Tuttavia, se si salva un valore di valuta come stringa formattata che include un simbolo di valuta, non può essere deserializzato in un sistema le cui impostazioni cultura predefinite utilizzano un simbolo di valuta diverso, come illustrato nell'esempio seguente.

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: {CultureInfo.CurrentCulture.DisplayName}");
      Console.WriteLine($"Currency Value: {value:C2}");

      // 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 '{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: {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 '{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'

È invece necessario serializzare il valore numerico insieme ad alcune informazioni culturali, 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. L'esempio seguente esegue questa operazione definendo una CurrencyValue struttura con due membri: il Decimal valore 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

Utilizzare impostazioni specifiche per cultura

In .NET la CultureInfo classe rappresenta una determinata cultura o area geografica. Alcune delle relative proprietà restituiscono oggetti che forniscono informazioni specifiche su alcuni aspetti di una cultura:

In generale, non fare ipotesi sui valori di proprietà specifiche CultureInfo e sui relativi oggetti correlati. Invece, è consigliabile considerare i dati specifici degli aspetti culturali come suscettibili di cambiamento, per questi motivi:

  • I singoli valori delle proprietà sono soggetti a modifiche e revisioni nel corso del tempo, man mano che i dati vengono corretti, i dati migliori diventano disponibili o le convenzioni specifiche delle impostazioni cultura cambiano.

  • I singoli valori delle proprietà possono variare in base alle versioni di .NET o del sistema operativo.

  • .NET supporta le culture sostitutive. In questo modo è possibile definire una nuova cultura personalizzata che integra le culture standard esistenti o sostituisce completamente una cultura standard esistente.

  • Nei sistemi Windows, l'utente può personalizzare le impostazioni specifiche della cultura usando l'app Area e lingua nel Pannello di controllo. Quando si crea un'istanza di un CultureInfo oggetto, è possibile determinare se riflette queste personalizzazioni utente chiamando il CultureInfo(String, Boolean) costruttore. In genere, per le app degli utenti finali, è necessario rispettare le preferenze utente in modo che l'utente venga presentato con i dati in un formato previsto.

Vedere anche