Share via


Globalisatie

Globalisatie omvat het ontwerpen en ontwikkelen van een wereldwijd kant-en-klare app die gelokaliseerde interfaces en regionale gegevens ondersteunt voor gebruikers in meerdere culturen. Voordat u begint met de ontwerpfase, moet u bepalen welke culturen uw app ondersteunt. Hoewel een app zich richt op één cultuur of regio als standaardinstelling, kunt u deze ontwerpen en schrijven, zodat deze eenvoudig kan worden uitgebreid naar gebruikers in andere culturen of regio's.

Als ontwikkelaars hebben we allemaal veronderstellingen over gebruikersinterfaces en gegevens die door onze culturen worden gevormd. Bijvoorbeeld, voor een Engels sprekende ontwikkelaar in de Verenigde Staten, het serialiseren van datum- en tijdgegevens als een tekenreeks in de indeling MM/dd/yyyy hh:mm:ss lijkt perfect redelijk. Het deserialiseren van die tekenreeks op een systeem in een andere cultuur zal echter waarschijnlijk een FormatException uitzondering veroorzaken of onjuiste gegevens produceren. Met globalisatie kunnen we dergelijke cultuurspecifieke aannames identificeren en ervoor zorgen dat ze geen invloed hebben op het ontwerp of de code van onze app.

In dit artikel worden enkele van de belangrijkste problemen besproken die u moet overwegen en de aanbevolen procedures die u kunt volgen bij het verwerken van tekenreeksen, datum- en tijdwaarden en numerieke waarden in een geglobaliseerde app.

Tekenreeksen

De verwerking van tekens en tekenreeksen is een centrale focus op globalisering, omdat elke cultuur of regio verschillende tekens en tekensets kan gebruiken en ze anders kan sorteren. Deze sectie bevat aanbevelingen voor het gebruik van tekenreeksen in geglobaliseerde apps.

Unicode intern gebruiken

.NET maakt standaard gebruik van Unicode-tekenreeksen. Een Unicode-tekenreeks bestaat uit nul, een of meer Char objecten, die elk een UTF-16-code-eenheid vertegenwoordigen. Er is een Unicode-weergave voor bijna elk teken in elke tekenset die overal ter wereld wordt gebruikt.

Veel toepassingen en besturingssystemen, waaronder het Windows-besturingssysteem, kunnen ook codepagina's gebruiken om tekensets weer te geven. Codepagina's bevatten doorgaans de standaard ASCII-waarden van 0x00 tot 0x7F en wijzen andere tekens toe aan de resterende waarden van 0x80 tot 0xFF. De interpretatie van waarden uit 0x80 via 0xFF is afhankelijk van de specifieke codepagina. Daarom moet u het gebruik van codepagina's in een geglobaliseerde app vermijden, indien mogelijk.

In het volgende voorbeeld ziet u de gevaren van het interpreteren van codepaginagegevens wanneer de standaardcodepagina op een systeem verschilt van de codepagina waarop de gegevens zijn opgeslagen. (Als u dit scenario wilt simuleren, geeft het voorbeeld expliciet verschillende codepagina's op.) In het voorbeeld wordt eerst een matrix gedefinieerd die bestaat uit de hoofdletters van het Griekse alfabet. Het codeert ze in een bytematrix met behulp van codepagina 737 (ook wel MS-DOS Grieks genoemd) en slaat de bytematrix op in een bestand. Als het bestand wordt opgehaald en de bytematrix wordt gedecodeerd met codepagina 737, worden de oorspronkelijke tekens hersteld. Als het bestand echter wordt opgehaald en de bytematrix wordt gedecodeerd met codepagina 1252 (of Windows-1252, die tekens in het Latijnse alfabet vertegenwoordigt), gaan de oorspronkelijke tekens verloren.

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:
'       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
'       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

Het gebruik van Unicode zorgt ervoor dat dezelfde code-eenheden altijd worden toegewezen aan dezelfde tekens en dat dezelfde tekens altijd worden toegewezen aan dezelfde bytematrices.

Resourcebestanden gebruiken

Zelfs als u een app ontwikkelt die is gericht op één cultuur of regio, moet u resourcebestanden gebruiken om tekenreeksen en andere resources op te slaan die worden weergegeven in de gebruikersinterface. U moet ze nooit rechtstreeks aan uw code toevoegen. Het gebruik van resourcebestanden heeft een aantal voordelen:

  • Alle tekenreeksen bevinden zich op één locatie. U hoeft niet in uw broncode te zoeken om tekenreeksen te identificeren die moeten worden gewijzigd voor een specifieke taal of cultuur.
  • U hoeft geen tekenreeksen te dupliceren. Ontwikkelaars die geen resourcebestanden gebruiken, definiëren vaak dezelfde tekenreeks in meerdere broncodebestanden. Deze duplicatie verhoogt de kans dat een of meer exemplaren over het hoofd worden gezien wanneer een tekenreeks wordt gewijzigd.
  • U kunt niet-tekenreeksresources, zoals afbeeldingen of binaire gegevens, opnemen in het resourcebestand in plaats van ze op te slaan in een afzonderlijk zelfstandig bestand, zodat ze eenvoudig kunnen worden opgehaald.

Het gebruik van resourcebestanden heeft bepaalde voordelen als u een gelokaliseerde app maakt. Wanneer u resources in satellietassembly's implementeert, selecteert de algemene taalruntime automatisch een cultuurresource op basis van de huidige UI-cultuur van de gebruiker, zoals gedefinieerd door de CultureInfo.CurrentUICulture eigenschap. Zolang u een geschikte cultuurspecifieke resource opgeeft en een object correct instantieert of een ResourceManager sterk getypte resourceklasse gebruikt, verwerkt de runtime de details van het ophalen van de juiste resources.

Zie Resourcebestanden maken voor meer informatie over het maken van resourcebestanden. Zie Satellietassembly's maken en resources verpakken en implementeren voor informatie over het maken en implementeren van satellietassembly's.

Tekenreeksen zoeken en vergelijken

Indien mogelijk moet u tekenreeksen als hele tekenreeksen afhandelen in plaats van ze als een reeks afzonderlijke tekens te verwerken. Dit is vooral belangrijk wanneer u sorteert of zoekt naar subtekenreeksen, om problemen te voorkomen die zijn gekoppeld aan het parseren van gecombineerde tekens.

Tip

U kunt de StringInfo klasse gebruiken om te werken met de tekstelementen in plaats van de afzonderlijke tekens in een tekenreeks.

Bij zoekopdrachten en vergelijkingen in tekenreeksen wordt de tekenreeks vaak behandeld als een verzameling tekens, die elk worden vertegenwoordigd door een Char object. In feite kan één teken worden gevormd door één, twee of meer Char objecten. Dergelijke tekens worden het vaakst gevonden in tekenreeksen van culturen waarvan de alfabetten bestaan uit tekens buiten het Latijnse tekenbereik Unicode Basic (U+0021 tot en met U+007E). In het volgende voorbeeld wordt geprobeerd de index van de LATIJNSE HOOFDLETTER A WITH GRAVE (U+00C0) in een tekenreeks te vinden. Dit teken kan echter op twee verschillende manieren worden weergegeven: als één code-eenheid (U+00C0) of als een samengesteld teken (twee code-eenheden: U+0041 en U+0300). In dit geval wordt het teken weergegeven in het tekenreeksexemplaren door twee Char objecten, U+0041 en U+0300. De voorbeeldcode roept de String.IndexOf(Char) en String.IndexOf(String) overbelastingen aan om de positie van dit teken in het tekenreeksexemplaren te vinden, maar deze retourneren verschillende resultaten. De eerste methodeaanroep heeft een Char argument; er wordt een rangtelvergelijking uitgevoerd en daarom kan er geen overeenkomst worden gevonden. De tweede aanroep heeft een String argument; het voert een cultuurgevoelige vergelijking uit en vindt daarom een overeenkomst.

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

U kunt enkele dubbelzinnigheid van dit voorbeeld voorkomen (aanroepen naar twee vergelijkbare overbelastingen van een methode die verschillende resultaten retourneert) door een overbelasting aan te roepen die een StringComparison parameter bevat, zoals de String.IndexOf(String, StringComparison) of String.LastIndexOf(String, StringComparison) methode.

Zoekopdrachten zijn echter niet altijd cultuurgevoelig. Als het doel van de zoekopdracht is om een beveiligingsbeslissing te nemen of toegang tot een bepaalde resource toe te staan of te weigeren, moet de vergelijking worden aangepast, zoals beschreven in de volgende sectie.

Tekenreeksen testen op gelijkheid

Als u twee tekenreeksen voor gelijkheid wilt testen in plaats van te bepalen hoe deze in de sorteervolgorde worden vergeleken, gebruikt u de String.Equals methode in plaats van een vergelijkingsmethode voor tekenreeksen, zoals String.Compare of CompareInfo.Compare.

Vergelijkingen voor gelijkheid worden doorgaans uitgevoerd voor toegang tot een bepaalde resource voorwaardelijk. U kunt bijvoorbeeld een vergelijking uitvoeren voor gelijkheid om een wachtwoord te verifiëren of om te bevestigen dat er een bestand bestaat. Dergelijke niet-taalkundige vergelijkingen moeten altijd ordinaal zijn in plaats van cultuurgevoelig. Over het algemeen moet u de instantiemethode String.Equals(String, StringComparison) of de statische String.Equals(String, String, StringComparison) methode aanroepen met een waarde voor StringComparison.Ordinal tekenreeksen, zoals wachtwoorden, en een waarde voor StringComparison.OrdinalIgnoreCase tekenreeksen, zoals bestandsnamen of URI's.

Vergelijkingen voor gelijkheid hebben soms betrekking op zoekopdrachten of subtekenreeksvergelijkingen in plaats van aanroepen naar de String.Equals methode. In sommige gevallen kunt u een subtekenreekszoekopdracht gebruiken om te bepalen of die subtekenreeks gelijk is aan een andere tekenreeks. Als het doel van deze vergelijking niet-taalkundige is, moet de zoekopdracht ook ordinaal zijn in plaats van cultuurgevoelig.

In het volgende voorbeeld ziet u het gevaar van een cultuurgevoelige zoekopdracht op niet-taalkundige gegevens. De AccessesFileSystem methode is ontworpen om toegang tot het bestandssysteem te verbieden voor URI's die beginnen met de subtekenreeks 'BESTAND'. Hiervoor wordt een cultuurgevoelige, hoofdlettergevoelige vergelijking van het begin van de URI uitgevoerd met de tekenreeks 'BESTAND'. Omdat een URI die toegang heeft tot het bestandssysteem, kan beginnen met 'FILE:' of 'file:', is de impliciete aanname dat 'i' (U+0069) altijd het equivalent van 'I' (U+0049) is. In het Turks en Azerbeidzjaans is de hoofdletterversie van "i" echter "İ" (U+0130). Vanwege deze discrepantie biedt de cultuurgevoelige vergelijking toegang tot het bestandssysteem wanneer dit verboden moet zijn.

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.

U kunt dit probleem voorkomen door een rangtelvergelijking uit te voeren die case negeert, zoals in het volgende voorbeeld wordt weergegeven.

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.

Tekenreeksen ordenen en sorteren

Normaal gesproken moeten geordende tekenreeksen die in de gebruikersinterface moeten worden weergegeven, worden gesorteerd op basis van cultuur. Voor het grootste deel worden dergelijke tekenreeksvergelijkingen impliciet verwerkt door .NET wanneer u een methode aanroept waarmee tekenreeksen worden gesorteerd, zoals Array.Sort of List<T>.Sort. Tekenreeksen worden standaard gesorteerd met behulp van de sorteerconventies van de huidige cultuur. In het volgende voorbeeld ziet u het verschil wanneer een matrix met tekenreeksen wordt gesorteerd met behulp van de conventies van de Engelse cultuur (Verenigde Staten) en de Zweedse cultuur (Zweden).

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

Cultuurgevoelige tekenreeksvergelijking wordt gedefinieerd door het CompareInfo object, dat wordt geretourneerd door de eigenschap van CultureInfo.CompareInfo elke cultuur. Cultuurgevoelige tekenreeksvergelijkingen die de String.Compare methode overbelasten, gebruiken ook het CompareInfo object.

.NET maakt gebruik van tabellen om cultuurgevoelige sorteringen uit te voeren op tekenreeksgegevens. De inhoud van deze tabellen, die gegevens bevatten over sorteergewichten en tekenreeksnormalisatie, wordt bepaald door de versie van de Unicode-standaard die is geïmplementeerd door een bepaalde versie van .NET. De volgende tabel bevat de versies van Unicode die zijn geïmplementeerd door de opgegeven versies van .NET. Deze lijst met ondersteunde Unicode-versies is alleen van toepassing op tekenvergelijking en sortering; deze is niet van toepassing op de classificatie van Unicode-tekens per categorie. Zie de sectie 'Tekenreeksen en de Unicode-standaard' in het String artikel voor meer informatie.

Versie van .NET Framework Besturingssysteem Unicode-versie
.NET Framework 2.0 Alle besturingssystemen Unicode 4.1
.NET Framework 3.0 Alle besturingssystemen Unicode 4.1
.NET Framework 3.5 Alle besturingssystemen Unicode 4.1
.NET Framework 4 Alle besturingssystemen Unicode 5.0
.NET Framework 4.5 en hoger Windows 7 Unicode 5.0
.NET Framework 4.5 en hoger Windows 8- en hogerbesturingssystemen Unicode 6.3.0
.NET Core en .NET 5+ Is afhankelijk van de versie van de Unicode Standard die wordt ondersteund door het onderliggende besturingssysteem.

Vanaf .NET Framework 4.5 en in alle versies van .NET Core en .NET 5+ is het vergelijken en sorteren van tekenreeksen afhankelijk van het besturingssysteem. .NET Framework 4.5 en hoger die worden uitgevoerd in Windows 7 haalt gegevens op uit de eigen tabellen die Unicode 5.0 implementeren. .NET Framework 4.5 en hoger die wordt uitgevoerd op Windows 8 en hoger haalt gegevens op uit besturingssysteemtabellen die Unicode 6.3 implementeren. Op .NET Core en .NET 5+ is de ondersteunde versie van Unicode afhankelijk van het onderliggende besturingssysteem. Als u cultuurgevoelige gesorteerde gegevens serialiseert, kunt u de SortVersion klasse gebruiken om te bepalen wanneer uw geserialiseerde gegevens moeten worden gesorteerd, zodat deze consistent zijn met .NET en de sorteervolgorde van het besturingssysteem. Zie het SortVersion klasonderwerp voor een voorbeeld.

Als uw app uitgebreide cultuurspecifieke soorten tekenreeksgegevens uitvoert, kunt u met de SortKey klasse werken om tekenreeksen te vergelijken. Een sorteersleutel weerspiegelt de cultuurspecifieke sorteergewichten, inclusief de alfabetische, hoofdletter- en diakritische gewichten van een bepaalde tekenreeks. Omdat vergelijkingen met behulp van sorteersleutels binair zijn, zijn ze sneller dan vergelijkingen die impliciet of expliciet een CompareInfo object gebruiken. U maakt een cultuurspecifieke sorteersleutel voor een bepaalde tekenreeks door de tekenreeks door te geven aan de CompareInfo.GetSortKey methode.

Het volgende voorbeeld is vergelijkbaar met het vorige voorbeeld. In plaats van de Array.Sort(Array) methode aan te roepen, waarmee de methode impliciet wordt CompareInfo.Compare aangeroepen, wordt echter een System.Collections.Generic.IComparer<T> implementatie gedefinieerd waarmee sorteersleutels worden vergeleken, die door deze methode wordt geïnstitueert en doorgegeven aan de Array.Sort<T>(T[], IComparer<T>) methode.

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

Tekenreekssamenvoeging voorkomen

Vermijd, indien mogelijk, samengestelde tekenreeksen te gebruiken die tijdens runtime zijn gebouwd vanuit samengevoegde woordgroepen. Samengestelde tekenreeksen zijn moeilijk te lokaliseren, omdat ze vaak een grammaticale volgorde aannemen in de oorspronkelijke taal van de app die niet van toepassing is op andere gelokaliseerde talen.

Datums en tijden verwerken

Hoe u datum- en tijdwaarden verwerkt, is afhankelijk van of deze worden weergegeven in de gebruikersinterface of persistent zijn. In deze sectie worden beide gebruiksgegevens onderzocht. Ook wordt besproken hoe u verschillen in tijdzones en rekenkundige bewerkingen kunt afhandelen wanneer u met datums en tijden werkt.

Datums en tijden weergeven

Wanneer datums en tijden doorgaans worden weergegeven in de gebruikersinterface, moet u de opmaakconventies van de cultuur van de gebruiker gebruiken, die wordt gedefinieerd door de CultureInfo.CurrentCulture eigenschap en het DateTimeFormatInfo object dat door de CultureInfo.CurrentCulture.DateTimeFormat eigenschap wordt geretourneerd. De opmaakconventies van de huidige cultuur worden automatisch gebruikt wanneer u een datum opmaakt met behulp van een van deze methoden:

In het volgende voorbeeld worden gegevens over zonsopgang en zonsondergang twee keer weergegeven voor 11 oktober 2012. Eerst wordt de huidige cultuur ingesteld op Kroatisch (Kroatië) en vervolgens op Engels (Verenigd Koninkrijk). In elk geval worden de datums en tijden weergegeven in de indeling die geschikt is voor die cultuur.

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

Datums en tijden behouden

U moet nooit datum- en tijdgegevens behouden in een indeling die per cultuur kan variëren. Dit is een veelvoorkomende programmeerfout die leidt tot beschadigde gegevens of een runtime-uitzondering. In het volgende voorbeeld worden twee datums, 9 januari 2013 en 18 augustus 2013, als tekenreeksen geserialiseerd met behulp van de opmaakconventies van de Engelse cultuur (Verenigde Staten). Wanneer de gegevens worden opgehaald en geparseerd met behulp van de conventies van de Engelse cultuur (Verenigde Staten), worden deze hersteld. Wanneer het echter wordt opgehaald en geparseerd met behulp van de conventies van de Engelse cultuur (Verenigd Koninkrijk), wordt de eerste datum ten onrechte geïnterpreteerd als 1 september en de tweede kan niet worden geparseerd omdat de Gregoriaanse kalender geen achttiende maand heeft.

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

U kunt dit probleem op drie manieren voorkomen:

  • Serialiseer de datum en tijd in binaire indeling in plaats van als een tekenreeks.
  • Sla de tekenreeksweergave van de datum en tijd op en parseert deze met behulp van een aangepaste notatietekenreeks die hetzelfde is, ongeacht de cultuur van de gebruiker.
  • Sla de tekenreeks op met behulp van de opmaakconventies van de invariante cultuur.

In het volgende voorbeeld ziet u de laatste benadering. Deze maakt gebruik van de opmaakconventies van de invariante cultuur die wordt geretourneerd door de statische CultureInfo.InvariantCulture eigenschap.

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

Serialisatie en tijdzonebewustzijn

Een datum- en tijdwaarde kan meerdere interpretaties hebben, variërend van een algemene tijd ('De winkels zijn geopend op 2 januari 2013 om 9:00 uur's') tot een bepaald tijdstip ('Geboortedatum: 2 januari 2013 6:32:00 uur'). Wanneer een tijdwaarde een bepaald tijdstip vertegenwoordigt en u deze herstelt vanuit een geserialiseerde waarde, moet u ervoor zorgen dat deze hetzelfde tijdstip vertegenwoordigt, ongeacht de geografische locatie of tijdzone van de gebruiker.

In het volgende voorbeeld ziet u dit probleem. Er wordt één lokale datum- en tijdwaarde opgeslagen als een tekenreeks in drie standaardindelingen:

  • "G" voor algemene datum lange tijd.
  • 's' voor sorteerbare datum/tijd.
  • "o" voor retourdatum/-tijd.
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

Wanneer de gegevens worden hersteld op een systeem in dezelfde tijdzone als het systeem waarop de gegevens zijn geserialiseerd, geven de gedeserialiseerde datum- en tijdwaarden de oorspronkelijke waarde nauwkeurig weer, zoals in de uitvoer wordt weergegeven:

'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

Als u echter de gegevens op een systeem in een andere tijdzone herstelt, blijven alleen de datum- en tijdwaarde die is opgemaakt met de standaardnotatiereeks 'o' (round-trip) de tijdzone-informatie behouden en vertegenwoordigt daarom hetzelfde moment in de tijd. Hier ziet u de uitvoer wanneer de datum- en tijdgegevens worden hersteld op een systeem in de tijdzone Romance Standard Time:

'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

Als u een datum- en tijdwaarde wilt weergeven die één moment van tijd vertegenwoordigt, ongeacht de tijdzone van het systeem waarop de gegevens worden gedeserialiseerd, kunt u een van de volgende handelingen uitvoeren:

  • Sla de waarde op als een tekenreeks met de standaardnotatietekenreeks o (round-trip). Deserialiseer het vervolgens op het doelsysteem.
  • Converteer deze naar UTC en sla deze op als een tekenreeks met de standaardnotatietekenreeks 'r' (RFC1123). Deserialiseer het vervolgens op het doelsysteem en converteer het naar lokale tijd.
  • Converteer deze naar UTC en sla deze op als een tekenreeks met behulp van de standaardnotatietekenreeks 'u' (universeel sorteerbaar). Deserialiseer het vervolgens op het doelsysteem en converteer het naar lokale tijd.

In het volgende voorbeeld ziet u elke techniek.

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

Wanneer de gegevens worden geserialiseerd op een systeem in de Pacific Standard Time-zone en worden gedeserialiseerd op een systeem in de Romaanse standaardtijdzone, wordt in het voorbeeld de volgende uitvoer weergegeven:

'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

Zie Tijd tussen tijdzones converteren voor meer informatie.

Rekenkundige datum- en tijdbewerkingen uitvoeren

Zowel de als DateTimeOffset de DateTime typen ondersteunen rekenkundige bewerkingen. U kunt het verschil tussen twee datumwaarden berekenen of bepaalde tijdsintervallen optellen of aftrekken van of van een datumwaarde. Rekenkundige bewerkingen op datum- en tijdwaarden nemen echter geen rekening met tijdzones en regels voor het aanpassen van tijdzones. Daarom kunnen datum- en tijdberekeningen op waarden die momenten in de tijd vertegenwoordigen, onnauwkeurige resultaten retourneren.

De overgang van Pacific Standard Time naar Pacific Daylight Time vindt bijvoorbeeld plaats op de tweede zondag van maart, dat is 10 maart voor het jaar 2013. Zoals in het volgende voorbeeld wordt weergegeven, geldt dat als u de datum en tijd berekent die 48 uur na 9 maart 2013 om 10:30 uur op een systeem in de Pacific Standard Time-zone ligt, het resultaat, 11 maart 2013 om 10:30 uur, geen rekening met de tussenliggende tijdaanpassing.

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

Volg deze stappen om ervoor te zorgen dat een rekenkundige bewerking op datum- en tijdwaarden nauwkeurige resultaten oplevert:

  1. Converteer de tijd in de brontijdzone naar UTC.
  2. Voer de rekenkundige bewerking uit.
  3. Als het resultaat een datum- en tijdwaarde is, converteert u deze van UTC naar de tijd in de brontijdzone.

Het volgende voorbeeld is vergelijkbaar met het vorige voorbeeld, behalve dat het deze drie stappen volgt om 48 uur correct toe te voegen aan 9 maart 2013 om 10:30 uur

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

Zie Rekenkundige bewerkingen uitvoeren met datums en tijden voor meer informatie.

Cultuurgevoelige namen gebruiken voor datumelementen

Uw app moet mogelijk de naam van de maand of de dag van de week weergeven. Hiervoor is code zoals het volgende gebruikelijk.

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.

Deze code retourneert echter altijd de namen van de dagen van de week in het Engels. Code waarmee de naam van de maand wordt geëxtraheerd, is vaak nog inflexibeler. Er wordt vaak uitgegaan van een kalender van twaalf maanden met namen van maanden in een specifieke taal.

Door aangepaste datum- en tijdnotatietekenreeksen of de eigenschappen van het DateTimeFormatInfo object te gebruiken, kunt u eenvoudig tekenreeksen extraheren die de namen van dagen van de week of maanden in de cultuur van de gebruiker weerspiegelen, zoals in het volgende voorbeeld wordt geïllustreerd. De huidige cultuur verandert in Frans (Frankrijk) en geeft de naam weer van de dag van de week en de naam van de maand voor 1 juli 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.

Numerieke waarden

De verwerking van getallen is afhankelijk van of ze worden weergegeven in de gebruikersinterface of persistent zijn. In deze sectie worden beide gebruiksgegevens onderzocht.

Notitie

Bij het parseren en opmaken herkent .NET alleen de Latijnse basistekens 0 tot en met 9 (U+0030 tot en met U+0039) als numerieke cijfers.

Numerieke waarden weergeven

Wanneer getallen doorgaans worden weergegeven in de gebruikersinterface, moet u de opmaakconventies van de cultuur van de gebruiker gebruiken, die wordt gedefinieerd door de CultureInfo.CurrentCulture eigenschap en door het NumberFormatInfo object dat door de CultureInfo.CurrentCulture.NumberFormat eigenschap wordt geretourneerd. De opmaakconventies van de huidige cultuur worden automatisch gebruikt wanneer u een datum op de volgende manieren opmaakt:

  • Met behulp van de parameterloze ToString methode van elk numeriek type.
  • Gebruik de ToString(String) methode van elk numeriek type, dat een notatietekenreeks als argument bevat.
  • Samengestelde opmaak gebruiken met numerieke waarden.

In het volgende voorbeeld wordt de gemiddelde temperatuur per maand weergegeven in Parijs, Frankrijk. Eerst wordt de huidige cultuur ingesteld op Frans (Frankrijk) voordat de gegevens worden weergegeven en vervolgens ingesteld op Engels (Verenigde Staten). In elk geval worden de maandnamen en temperaturen weergegeven in de indeling die geschikt is voor die cultuur. Houd er rekening mee dat de twee culturen verschillende decimale scheidingstekens gebruiken in de temperatuurwaarde. Houd er ook rekening mee dat in het voorbeeld de tekenreeks voor aangepaste datum- en tijdnotatie 'MMMM' wordt gebruikt om de volledige maandnaam weer te geven en dat hiermee de juiste hoeveelheid ruimte voor de maandnaam in de resultaattekenreeks wordt toegewezen door de lengte van de langste maandnaam in de DateTimeFormatInfo.MonthNames matrix te bepalen.

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

Numerieke waarden behouden

U moet nooit numerieke gegevens in een cultuurspecifieke indeling behouden. Dit is een veelvoorkomende programmeerfout die leidt tot beschadigde gegevens of een runtime-uitzondering. In het volgende voorbeeld worden tien willekeurige getallen met drijvende komma gegenereerd en vervolgens geserialiseerd als tekenreeksen met behulp van de opmaakconventies van de Engelse cultuur (Verenigde Staten). Wanneer de gegevens worden opgehaald en geparseerd met behulp van de conventies van de Engelse cultuur (Verenigde Staten), worden deze hersteld. Wanneer het echter wordt opgehaald en geparseerd met behulp van de conventies van de Franse cultuur (Frankrijk), kan geen van de getallen worden geparseerd omdat de culturen verschillende decimale scheidingstekens gebruiken.

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'

U kunt dit probleem voorkomen door een van de volgende technieken te gebruiken:

  • Sla de tekenreeksweergave van het getal op en parseert deze met behulp van een aangepaste notatietekenreeks die hetzelfde is, ongeacht de cultuur van de gebruiker.
  • Sla het getal op als een tekenreeks met behulp van de opmaakconventies van de invariante cultuur, die wordt geretourneerd door de CultureInfo.InvariantCulture eigenschap.

Het serialiseren van valutawaarden is een speciaal geval. Omdat een valutawaarde afhankelijk is van de valuta-eenheid waarin deze wordt uitgedrukt, is het weinig zinvol om deze als een onafhankelijke numerieke waarde te behandelen. Als u echter een valutawaarde opslaat als een opgemaakte tekenreeks die een valutasymbool bevat, kan deze niet worden gedeserialiseerd op een systeem waarvan de standaardcultuur gebruikmaakt van een ander valutasymbool, zoals in het volgende voorbeeld wordt weergegeven.

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 plaats daarvan moet u de numerieke waarde serialiseren, samen met enkele culturele informatie, zoals de naam van de cultuur, zodat de waarde en het valutasymbool onafhankelijk van de huidige cultuur kunnen worden gedeserialiseerd. In het volgende voorbeeld wordt dit door een CurrencyValue structuur met twee leden te definiëren: de Decimal waarde en de naam van de cultuur waartoe de waarde behoort.

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

Werken met cultuurspecifieke instellingen

In .NET vertegenwoordigt de CultureInfo klasse een bepaalde cultuur of regio. Sommige eigenschappen retourneren objecten die specifieke informatie geven over een bepaald aspect van een cultuur:

Over het algemeen maakt u geen veronderstellingen over de waarden van specifieke CultureInfo eigenschappen en hun gerelateerde objecten. In plaats daarvan moet u cultuurspecifieke gegevens weergeven als onderhevig aan wijzigingen, om deze redenen:

  • Afzonderlijke eigenschapswaarden zijn in de loop van de tijd onderhevig aan wijzigingen en revisies, omdat gegevens worden gecorrigeerd, er betere gegevens beschikbaar komen of cultuurspecifieke conventies veranderen.

  • Afzonderlijke eigenschapswaarden kunnen verschillen per versie van .NET of besturingssysteemversies.

  • .NET ondersteunt vervangingsculturen. Dit maakt het mogelijk om een nieuwe aangepaste cultuur te definiëren die een aanvulling vormt op bestaande standaardculturen of een bestaande standaardcultuur volledig vervangt.

  • Op Windows-systemen kan de gebruiker cultuurspecifieke instellingen aanpassen met behulp van de app Regio en Taal in Configuratiescherm. Wanneer u een CultureInfo object instantieert, kunt u bepalen of het deze gebruikersaanpassingen weerspiegelt door de CultureInfo(String, Boolean) constructor aan te roepen. Doorgaans moet u voor eindgebruikers-apps gebruikersvoorkeuren respecteren, zodat de gebruiker gegevens in een indeling krijgt die ze verwachten.

Zie ook