Freigeben über


Globalisierung

Globalisierung bedeutet Gestaltung und Entwicklung von Apps, die lokalisierte Benutzeroberflächen und regionale Daten für Benutzer verschiedener Kulturen unterstützen. Bevor Sie mit der Entwurfsphase beginnen, sollten Sie festlegen, welche Kulturen die App unterstützen soll. Auch wenn eine App standardmäßig auf eine einzige Kultur oder Region ausgerichtet ist, kann Sie so entworfen und geschrieben werden, dass sie leicht auf Benutzer in anderen Kulturen oder Regionen ausgeweitet werden kann.

Als Entwickler haben wir alle bestimmte Annahmen über Benutzeroberflächen und Daten, die durch unsere Kulturen geformt werden. Für einen englischsprachigen Entwickler in den USA erscheint das Serialisieren von Datums- und Uhrzeitangaben als Zeichenfolge im Format MM/dd/yyyy hh:mm:ss absolut plausibel. Das Deserialisieren dieser Zeichenfolge auf einem System in einer anderen Kultur könnte jedoch eine FormatException-Ausnahme auslösen oder falsche Daten generieren. Durch Globalisierung können wir solche kulturspezifischen Annahmen identifizieren sowie sicherstellen, dass sie den Entwurf oder den Code der App nicht beeinträchtigen.

In diesem Artikel werden einige zu beachtende Probleme erläutert und bewährte Methoden zur Behandlung von Zeichenfolgen, Datums- und Uhrzeitwerten sowie numerischen Werten in einer globalisierten App vorgestellt.

Zeichenfolgen

Die Behandlung von Zeichen und Zeichenfolgen ist ein zentraler Schwerpunkt der Globalisierung, da jede Kultur bzw. Region u. U. unterschiedliche Zeichen und Zeichensätze verwendet und diese anders sortiert. Dieser Abschnitt enthält Empfehlungen für die Verwendung von Zeichenfolgen in globalisierten Apps.

Interne Verwendung von Unicode

Standardmäßig verwendet .NET Unicode-Zeichenfolgen. Eine Unicode-Zeichenfolge besteht aus null, einem oder mehreren Char-Objekten, die jeweils eine UTF-16-Codeeinheit darstellen. Es gibt eine Unicode-Darstellung für nahezu jedes Zeichen in allen weltweit verwendeten Zeichensätzen.

Viele Anwendungen und Betriebssysteme, einschließlich das Windows-Betriebssystem, können auch Codepages verwenden, um Zeichensätze darzustellen. Codepages enthalten normalerweise die ASCII-Standardwerte von 0x00 bis 0x7F und ordnen andere Zeichen den restlichen Werten von 0x80 bis 0xFF zu. Die Interpretation der Werte von 0x80 bis 0xFF ist von der jeweiligen Codepage abhängig. Daher sollten Sie Codepages, soweit möglich, in globalisierten Apps vermeiden.

Das folgende Beispiel veranschaulicht die Risiken beim Interpretieren von Codepagedaten, wenn sich die Standardcodepage in einem System von der Codepage unterscheidet, auf der die Daten gespeichert wurden. (Um dieses Szenario zu simulieren, werden im Beispiel explizit unterschiedliche Codepages angegeben.) Zunächst wird im Beispiel ein Array definiert, das aus den Großbuchstaben des griechischen Alphabets besteht. Diese werden durch die Verwendung von Codepage 737 (auch als MS-DOS Greek bezeichnet) in ein Bytearray codiert, das in eine Datei geschrieben wird. Wenn die Datei abgerufen und das Bytearray mit Codepage 737 decodiert wird, werden die ursprünglichen Zeichen wiederhergestellt. Wenn die Datei dagegen abgerufen und das Bytearray mit Codepage 1252 (bzw. Windows-1252 zur Darstellung der Zeichen im lateinischen Alphabet) decodiert wird, gehen die ursprünglichen Zeichen 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:
'       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
'       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

Durch die Verwendung von Unicode wird sichergestellt, dass die gleichen Codeeinheiten stets den gleichen Zeichen zugeordnet werden und dass die gleichen Zeichen immer den gleichen Bytearrays zugeordnet werden.

Verwendung von Ressourcendateien

Auch wenn Sie eine App entwickeln, die auf eine einzige Kultur oder Region ausgerichtet ist, sollten Sie Ressourcendateien zum Speichern von Zeichenfolgen und anderen Ressourcen verwenden, die auf der Benutzeroberfläche angezeigt werden. Sie sollten diese niemals direkt in den Code einfügen. Die Verwendung von Ressourcendateien bietet eine Reihe von Vorteilen:

  • Alle Zeichenfolgen befinden sich an einem einzigen Ort. Sie müssen nicht den gesamten Quellcode nach Zeichenfolgen durchsuchen, die für eine bestimmte Sprache oder Kultur geändert werden müssen.
  • Zeichenfolgen müssen nicht dupliziert werden. Entwickler, die keine Ressourcendateien verwenden, definieren die gleiche Zeichenfolge oft in mehreren Quellcodedateien. Diese Duplizierung erhöht die Wahrscheinlichkeit, dass Instanzen übersehen werden, wenn eine Zeichenfolge geändert wird.
  • Sie können Nicht-Zeichenfolgenressourcen wie Bilder oder Binärdaten in die Ressourcendatei aufnehmen, anstatt diese in einer eigenständigen Datei zu speichern, sodass sie leicht abgerufen werden können.

Die Verwendung von Ressourcendateien hat insbesondere dann Vorteile, wenn Sie eine lokalisierte App erstellen. Wenn Sie Ressourcen in Satellitenassemblys bereitstellen, wählt die Common Language Runtime automatisch eine der Kultur entsprechende Ressource anhand der aktuellen Kultur der Benutzeroberfläche aus, wie von der CultureInfo.CurrentUICulture-Eigenschaft definiert. Solange Sie eine entsprechende kulturspezifische Ressource bereitstellen und ein ResourceManager-Objekt ordnungsgemäß instanziieren oder eine stark typisierte Ressourcenklasse verwenden, werden die Details zum Abrufen der entsprechenden Ressourcen von der Laufzeit behandelt.

Weitere Informationen zum Erstellen von Ressourcendateien finden Sie unter Erstellen von Ressourcendateien. Informationen zum Erstellen und Bereitstellen von Satellitenassemblys finden Sie unter Erstellen von Satellitenassemblys und Packen und Bereitstellen von Ressourcen.

Suchen und Vergleichen von Zeichenfolgen

Behandeln Sie Zeichenfolgen möglichst als ganze Zeichenfolgen und nicht als eine Reihe einzelner Zeichen. Dies ist besonders wichtig, wenn Sie Teilzeichenfolgen sortieren oder danach suchen, um Probleme beim Analysieren von kombinierten Zeichen zu vermeiden.

Tipp

Sie können die StringInfo-Klasse verwenden, um mit den Textelementen anstelle der einzelnen Zeichen in einer Zeichenfolge zu arbeiten.

Beim Suchen und Vergleichen von Zeichenfolgen wird häufig der Fehler begangen, die Zeichenfolge als Auflistung von Zeichen zu behandeln, die jeweils durch ein Char-Objekt dargestellt werden. Tatsächlich wird ein einzelnes Zeichen u. U. durch ein oder mehrere Char-Objekte gebildet. Solche Zeichen werden am häufigsten in den Zeichenfolgen von Kulturen gefunden, deren Alphabet aus Zeichen außerhalb des lateinischen Unicode-Standardzeichenbereichs (U+0021 bis U+007E) besteht. Im folgenden Beispiel wird versucht, den Index des LATEINISCHEN GROSSBUCHSTABENS A MIT GRAVIS (U+00C0) in einer Zeichenfolge zu suchen. Dieses Zeichen kann jedoch auf zwei verschiedene Arten dargestellt werden: als einzelne Codeeinheit (U+00C0) oder als zusammengesetztes Zeichen (zwei Codeeinheiten: U+0041 und U+0300). In diesem Fall wird das Zeichen in der Zeichenfolgeninstanz von zwei Char-Objekten dargestellt, U+0041 und U+0300. Der Beispielcode ruft die String.IndexOf(Char)- und die String.IndexOf(String)-Überladung auf, um die Position des Zeichens in der Zeichenfolgeninstanz zu suchen. Diese geben jedoch unterschiedliche Ergebnisse zurück. Der erste Methodenaufruf weist ein Char-Argument auf. Dieser führt einen ordinalen Vergleich durch und findet daher keine Übereinstimmung. Der zweite Aufruf weist ein String-Argument auf. Dieser führt einen kulturabhängigen Vergleich durch und findet daher eine Übereinstimmung.

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

Sie können die Mehrdeutigkeit in diesem Beispiel (Aufrufe an zwei ähnliche Überladungen einer Methode, die unterschiedliche Ergebnisse zurückgeben) teilweise vermeiden, indem Sie eine Überladung aufrufen, die einen StringComparison-Parameter wie z. B. String.IndexOf(String, StringComparison) oder String.LastIndexOf(String, StringComparison) enthält.

Suchen sind jedoch nicht immer kulturabhängig. Wenn der Zweck der Suche darin besteht, eine Sicherheitsentscheidung zu treffen oder Zugriff auf eine Ressource zu gewähren bzw. nicht zu gewähren, sollte der Vergleich ordinal sein, wie im nächsten Abschnitt erläutert wird.

Testen von Zeichenfolgen auf Gleichheit

Bestimmen Sie zum Testen von zwei Zeichenfolgen auf Gleichheit nicht deren Übereinstimmung in der Sortierreihenfolge, sondern verwenden Sie die Methode String.Equals anstelle einer Methode zum Zeichenfolgenvergleich wie String.Compare oder CompareInfo.Compare.

Vergleiche auf Gleichheit werden in der Regel durchgeführt, um bedingt auf eine Ressource zuzugreifen. Beispielsweise können Sie einen Vergleich auf Gleichheit durchführen, um ein Kennwort zu überprüfen oder das Vorhandensein einer Datei zu bestätigen. Solche nicht linguistischen Vergleiche sollten immer ordinal anstatt kulturabhängig sein. Im Allgemeinen sollten Sie die String.Equals(String, StringComparison)-Instanzmethode Methode oder die statische String.Equals(String, String, StringComparison)-Methode mit dem Wert StringComparison.Ordinal für Zeichenfolgen wie Kennwörter und mit dem Wert StringComparison.OrdinalIgnoreCase für Zeichenfolgen wie Dateinamen oder URIs aufrufen.

Vergleiche auf Gleichheit umfassen gelegentlich Suchen oder Teilzeichenfolgenvergleiche anstelle von Aufrufen der String.Equals-Methode. In einigen Fällen können Sie eine Teilzeichenfolgensuche verwenden, um zu bestimmen, ob diese Teilzeichenfolge einer anderen Zeichenfolge entspricht. Wenn der Zweck dieses Vergleiches nicht linguistisch ist, sollte die Suche ebenfalls ordinal anstatt kulturabhängig sein.

Das folgende Beispiel veranschaulicht das Risiko einer kulturabhängigen Suche bei nicht linguistischen Daten. Die AccessesFileSystem-Methode wurde entworfen, um den Dateisystemzugriff für URIs zu verweigern, die mit der Teilzeichenfolge "FILE" beginnen. Hierzu wird ein kulturabhängiger Vergleich unter Beachtung von Groß- und Kleinschreibung zwischen dem Anfang des URI und der Zeichenfolge "FILE" durchgeführt. Da ein URI, der auf das Dateisystem zugreift, mit „FILE:“ oder „file:“ beginnen kann, wird implizit angenommen, dass „i“ (U+0069) immer die Kleinbuchstabenentsprechung von „I“ (U+0049) ist. Auf Türkisch und Aserbaidschanisch ist die Großbuchstabenversion von "i" jedoch "İ" (U+0130). Aufgrund dieser Diskrepanz gewährt der kulturabhängige Vergleich den Dateisystemzugriff, wenn er eigentlich verhindert werden soll.

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.

Sie können dieses Problem vermeiden, indem Sie einen ordinalen Vergleich ohne Beachtung von Groß- und Kleinschreibung ausführen, wie im folgenden Beispiel gezeigt wird.

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.

Ordnen und Sortieren von Zeichenfolgen

In der Regel sollten geordnete Zeichenfolgen, die auf der Benutzeroberfläche angezeigt werden, auf Grundlage der Kultur sortiert werden. In den meisten Fällen werden solche Zeichenfolgenvergleiche von .NET implizit verarbeitet, wenn Sie eine Methode aufrufen, die Zeichenfolgen sortiert, wie z.B. Array.Sort oder List<T>.Sort. Standardmäßig werden Zeichenfolgen anhand der Sortierkonventionen der aktuellen Kultur sortiert. Das folgende Beispiel veranschaulicht den Unterschied beim Sortieren eines Zeichenfolgenarrays mit den Konventionen der Kulturen Englisch (USA) und Schwedisch (Schweden).

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

Der kulturabhängige Zeichenfolgenvergleich wird durch das CompareInfo-Objekt definiert, das von der CultureInfo.CompareInfo-Eigenschaft jeder Kultur zurückgegeben wird. Kulturabhängige Zeichenfolgenvergleiche mit den String.Compare-Methodenüberladungen verwenden ebenfalls das CompareInfo-Objekt.

.NET verwendet Tabellen zum Durchführen von kulturabhängigen Sortierungen für Zeichenfolgendaten. Der Inhalt dieser Tabellen, die Daten zu Sortierungsgewichtungen und Zeichenfolgennormalisierung enthalten, wird durch die Version des Unicode-Standards festgelegt, der von einer bestimmten .NET-Version implementiert wird. Die folgende Tabelle enthält die Unicode-Versionen, die von den angegebenen .NET-Versionen implementiert werden. Diese Liste der unterstützten Unicode-Versionen gilt lediglich für den Zeichenvergleich und die Zeichensortierung. Sie gilt nicht für die kategorische Klassifizierung von Unicode-Zeichen. Weitere Informationen finden Sie im Abschnitt „Zeichenfolgen und Unicode-Standard“ im Artikel String.

.NET Framework-Version Betriebssystem Unicode-Version
.NET Framework 2.0 Alle Betriebssysteme Unicode 4.1
.NET Framework 3.0 Alle Betriebssysteme Unicode 4.1
.NET Framework 3.5 Alle Betriebssysteme Unicode 4.1
.NET Framework 4 Alle Betriebssysteme Unicode 5.0
.NET Framework 4.5 und höher Windows 7 Unicode 5.0
.NET Framework 4.5 und höher Windows 8 und neuere Betriebssysteme Unicode 6.3.0
.NET Core und .NET 5 und höher Hängt von der Version des Unicode-Standards ab, die vom zugrunde liegenden Betriebssystem unterstützt wird.

Ab .NET Framework 4.5 und in allen Versionen von .NET Core sowie .NET 5 und höher hängen der Vergleich und die Sortierung von Zeichenfolgen vom Betriebssystem ab. In .NET Framework 4.5 und höher werden unter Windows 7 Daten aus eigenen Tabellen abgerufen, die Unicode 5.0 implementieren. In .NET Framework 4.5 und höher werden unter Windows 8 und höher Daten aus Betriebssystemtabellen abgerufen, die Unicode 6.3 implementieren. Bei .NET Core sowie .NET 5 und höher hängt die unterstützte Unicode-Version vom zugrunde liegenden Betriebssystem ab. Wenn Sie kulturabhängige sortierte Daten serialisieren, können Sie mit der Klasse SortVersion bestimmen, wann die serialisierten Daten sortiert werden müssen, damit sie mit .NET und der Sortierreihenfolge des Betriebssystems konsistent sind. Ein Beispiel finden Sie im Thema zur SortVersion-Klasse.

Wenn Ihre App umfangreiche kulturspezifische Sortierungen von Zeichenfolgendaten ausführt, können Sie Zeichenfolgen mit der SortKey-Klasse vergleichen. Ein Sortierschlüssel spiegelt die kulturspezifischen Sortierungsgewichtungen wider, einschließlich alphabetischer, Groß-/Kleinschreibungs- sowie diakritischer Gewichtungen einer bestimmten Zeichenfolge. Da Vergleiche mit Sortierschlüsseln binär sind, werden diese schneller ausgeführt als Vergleiche unter impliziter oder expliziter Verwendung eines CompareInfo-Objekts. Sie erstellen einen kulturabhängigen Sortierschlüssel für eine bestimmte Zeichenfolge, indem Sie die Zeichenfolge an die CompareInfo.GetSortKey-Methode übergeben.

Das folgende Beispiel ähnelt dem vorherigen Beispiel. Anstatt jedoch die Array.Sort(Array)-Methode aufzurufen, die die CompareInfo.Compare-Methode implizit aufruft, wird eine System.Collections.Generic.IComparer<T>-Implementierung definiert, die Sortierschlüssel vergleicht, welche dann instanziiert und an die Array.Sort<T>(T[], IComparer<T>)-Methode übergeben werden.

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

Vermeiden von Zeichenfolgenverkettung

Vermeiden Sie, soweit möglich, die Verwendung zusammengesetzter Zeichenfolgen, die zur Laufzeit aus verketteten Ausdrücken erstellt werden. Zusammengesetzte Zeichenfolgen sind schwer zu lokalisieren, da sie häufig eine grammatische Reihenfolge in der Originalsprache der App voraussetzen, die nicht für andere lokalisierte Sprachen gilt.

Arbeiten mit Datums- und Zeitangaben

Wie Sie Datums- und Uhrzeitwerte behandeln, ist davon abhängig, ob diese auf der Benutzeroberfläche angezeigt oder beibehalten werden. In diesem Abschnitt werden beide Nutzungsarten behandelt. Außerdem wird erläutert, wie Sie Zeitzonenunterschiede und arithmetische Operationen beim Arbeiten mit Daten- und Uhrzeitwerten behandeln können.

Anzeigen von Datums- und Zeitangaben

Wenn Datumsangaben und Uhrzeiten auf der Benutzeroberfläche angezeigt werden, sollten Sie normalerweise die Formatierungskonventionen der Kultur des Benutzers verwenden, die durch die CultureInfo.CurrentCulture-Eigenschaft und das von der DateTimeFormatInfo-Eigenschaft zurückgegebene CultureInfo.CurrentCulture.DateTimeFormat-Objekt definiert wird. Die Formatierungskonventionen der aktuellen Kultur werden automatisch verwendet, wenn Sie ein Datum mit einer der folgenden Methoden formatieren:

Im folgenden Beispiel werden Daten zum Sonnenaufgang und Sonnenuntergang für den 11. Oktober 2012 angezeigt. Die aktuelle Kultur wird zuerst auf Kroatisch (Kroatien) und dann auf Englisch (Vereinigtes Königreich) festgelegt. In beiden Fällen werden die Datums- und Uhrzeitangaben im entsprechenden Format für die jeweilige Kultur angezeigt.

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

Beibehalten von Datums- und Zeitangaben

Sie sollten Datums- und Uhrzeitangaben niemals in einem Format beibehalten, das je nach Kultur variieren kann. Dies ist ein häufiger Programmierfehler, der zu beschädigten Daten oder einer Laufzeitausnahme führt. Im folgenden Beispiel werden zwei Datumsangaben, der 9. Januar 2013 und der 18. August 2013, unter Verwendung der Formatierungskonventionen der Kultur Englisch (USA) als Zeichenfolgen serialisiert. Wenn die Daten mit den Konventionen der Kultur Englisch (USA) abgerufen und analysiert werden, können sie erfolgreich wiederhergestellt werden. Wenn sie jedoch mit den Konventionen der Kultur Englisch (Großbritannien) abgerufen und analysiert werden, wird das erste Datum fälschlicherweise als 1. September interpretiert, und das zweite kann nicht analysiert werden, da der Gregorianische Kalender keinen 18. Monat aufweist.

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

Sie können dieses Problem auf drei Arten vermeiden:

  • Serialisieren Sie Datum und Uhrzeit im Binärformat anstatt als Zeichenfolge.
  • Speichern und analysieren Sie die Zeichenfolgendarstellung von Datum und Uhrzeit, indem Sie eine benutzerdefinierte Formatzeichenfolge verwenden, die von der Kultur des Benutzers unabhängig ist.
  • Speichern Sie die Zeichenfolge, indem Sie die Formatierungskonventionen der invarianten Kultur verwenden.

Der letzte Ansatz wird anhand des folgenden Beispiels veranschaulicht. Dabei werden die Formatierungskonventionen der invarianten Kultur verwendet, die von der statischen CultureInfo.InvariantCulture-Eigenschaft zurückgegeben wird.

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

Serialisierung und Unterstützung von Zeitzonen

Ein Datums- und Uhrzeitwert kann über mehrere Interpretationen verfügen, von der allgemeinen Uhrzeit („Die Läden öffnen am 2. Januar 2013 um 9:00 Uhr“) bis zu einem bestimmten Zeitpunkt („Geburtsdatum: 2. Januar 2013 6:32:00 Uhr“). Wenn ein Zeitwert einen bestimmten Zeitpunkt darstellt und aus einem serialisierten Wert wiederhergestellt wird, sollten Sie sicherstellen, dass er den gleichen Zeitpunkt unabhängig vom geografischen Ort oder der Zeitzone des Benutzers repräsentiert.

Dieses Problem wird anhand des folgenden Beispiels veranschaulicht. Ein einzelner lokaler Datums- und Uhrzeitwert wird als Zeichenfolge in drei Standardformaten gespeichert:

  • „G“ für generelles Datum, lange Zeit.
  • „s“ für sortierbare(s) Datum/Uhrzeit.
  • „o“ für Roundtrip für Datum/Uhrzeit.
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

Wenn die Daten auf einem System in der gleichen Zeitzone wiederhergestellt werden, in der sie auch serialisiert wurden, spiegeln die deserialisierten Datums- und Uhrzeitwerte den ursprünglichen Wert wider, wie die Ausgabe zeigt:

'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

Wenn Sie die Daten jedoch auf einem System in einer anderen Zeitzone wiederherstellen, behält nur der Datums- und Uhrzeitwert, der mit der Standardformatzeichenfolge "o" (Roundtrip) formatiert wurde, die Zeitzoneninformationen bei und stellt daher den gleichen Zeitpunkt dar. Wenn die Datums- und Uhrzeitdaten auf einem System in der Zeitzone Romanische Normalzeit wiederhergestellt werden, lautet die Ausgabe wie folgt:

'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

Um einen Datums- und Uhrzeitwert exakt widerzuspiegeln, der einen einzigen Zeitpunkt unabhängig von der Zeitzone des Systems darstellt, auf dem die Daten deserialisiert werden, können Sie einen der folgenden Schritte ausführen:

  • Speichern Sie den Wert als Zeichenfolge, indem Sie die Standardformatzeichenfolge "o" (Roundtrip) verwenden. Deserialisieren Sie ihn anschließend auf dem Zielsystem.
  • Konvertieren Sie ihn in UTC, und speichern Sie ihn als Zeichenfolge, indem Sie die Standardformatzeichenfolge "r" (RFC1123 ) verwenden. Deserialisieren Sie ihn anschließend auf dem Zielsystem, und konvertieren Sie ihn in die Ortszeit.
  • Konvertieren Sie ihn in UTC, und speichern Sie ihn als Zeichenfolge, indem Sie die Standardformatzeichenfolge "u" (universell sortierbar) verwenden. Deserialisieren Sie ihn anschließend auf dem Zielsystem, und konvertieren Sie ihn in die Ortszeit.

Das folgende Beispiel veranschaulicht die einzelnen Techniken:

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

Wenn die Daten auf einem System in der Zeitzone Pazifik Normalzeit serialisiert und auf einem System in der Zeitzone Romanische Normalzeit deserialisiert werden, lautet die Ausgabe wie folgt:

'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

Weitere Informationen finden Sie unter Konvertieren von Uhrzeiten zwischen Zeitzonen.

Ausführen von arithmetischen Datums- und Uhrzeitoperationen

Der DateTime- und der DateTimeOffset-Typ unterstützen arithmetische Operationen. Sie können die Differenz zwischen zwei Datumswerten berechnen oder bestimmte Zeitintervalle zu einem Datumswert addieren oder davon subtrahieren. Jedoch werden bei arithmetischen Operationen für Datums- und Uhrzeitwerte keine Zeitzonen oder Regeln zur Anpassung der Zeitzone berücksichtigt. Daher kann eine Datums- und Uhrzeitarithmetik für Werte, die Zeitpunkte darstellen, fehlerhafte Ergebnisse zurückgeben.

Beispielsweise erfolgt die Umstellung von der pazifischen Normalzeit auf die pazifische Sommerzeit am zweiten Sonntag im März. Im Jahr 2013 ist dies der 10. März. Wenn Sie auf einem System in der Zeitzone Pazifik Normalzeit das Datum und die Uhrzeit berechnen, die 48 Stunden nach dem 9. März 2013 um 10:30 Uhr liegen, wird die dazwischenliegende Zeitumstellung beim Ergebnis, dem 11. März 2013 um 10:30 Uhr, nicht berücksichtigt, wie das folgende Beispiel zeigt.

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

Um sicherzustellen, dass eine arithmetische Operation für Datums- und Uhrzeitwerte akkurate Ergebnisse liefert, führen Sie folgende Schritte aus:

  1. Konvertieren Sie die Zeit in der Quellzeitzone in UTC.
  2. Führen Sie die arithmetische Operation aus.
  3. Wenn das Ergebnis ein Datums- und Uhrzeitwert ist, konvertieren Sie diesen von UTC in die Uhrzeit der Quellzeitzone.

Das folgende Beispiel gleicht dem vorhergehenden bis auf die folgenden drei Schritte, um zum 9. März 2013 um 10:30 Uhr korrekt 48 Stunden zu addieren.

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

Weitere Informationen finden Sie unter Durchführen arithmetischer Datums- und Uhrzeitoperationen.

Verwenden kulturabhängiger Namen für Datumselemente

Ihre App muss möglicherweise den Namen des Monats oder des Wochentags anzeigen. Hierzu wird normalerweise ein Code wie der folgende verwendet.

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.

Allerdings gibt dieser Code immer die Namen der Wochentage auf Englisch zurück. Code, der den Namen des Monats extrahiert, ist oft noch weniger flexibel. Oft wird von einem Zwölf-Monats-Kalender mit Namen von Monaten in einer bestimmten Sprache ausgegangen.

Durch benutzerdefinierte Formatzeichenfolgen für Datum und Uhrzeit oder die Eigenschaften des DateTimeFormatInfo-Objekts können Zeichenfolgen, die die Namen von Wochentagen oder Monaten in der Kultur des Benutzers darstellen, leicht extrahiert werden. Dies wird im folgenden Beispiel veranschaulicht. Die aktuelle Kultur wird in Französisch (Frankreich) geändert, und die Namen des Wochentags und des Monats werden für den 1. Juli 2013 angezeigt.

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.

Numerische Werte

Die Behandlung von Zahlen ist davon abhängig, ob diese auf der Benutzeroberfläche angezeigt oder beibehalten werden. In diesem Abschnitt werden beide Nutzungsarten behandelt.

Hinweis

Bei Analyse- und Formatierungsvorgängen erkennt .NET nur die lateinischen Standardzeichen 0 bis 9 (U+0030 bis U+0039) als numerische Zeichen.

Anzeigen numerischer Werte

Wenn Zahlen auf der Benutzeroberfläche angezeigt werden, sollten Sie normalerweise die Formatierungskonventionen der Kultur des Benutzers verwenden, die durch die CultureInfo.CurrentCulture-Eigenschaft und das von der NumberFormatInfo-Eigenschaft zurückgegebene CultureInfo.CurrentCulture.NumberFormat-Objekt definiert wird. Die Formatierungskonventionen der aktuellen Kultur werden automatisch verwendet, wenn Sie ein Datum mit einer der folgenden Methoden formatieren:

  • Verwenden der parameterlosen ToString-Methode eines beliebigen numerischen Typs.
  • Verwenden der ToString(String)-Methode eines beliebigen numerischen Typs, der eine Formatzeichenfolge als Argument enthält.
  • Verwenden der zusammengesetzten Formatierung mit numerischen Werten.

Im folgenden Beispiel wird die Durchschnittstemperatur pro Monat in Paris, Frankreich, angezeigt. Zuerst wird die aktuelle Kultur auf Französisch (Frankreich) festgelegt, bevor die Daten anzeigt werden. Anschließend wird die Kultur auf Englisch (USA) festgelegt. In beiden Fällen werden die Monatsnamen und Temperaturen im entsprechenden Format für die jeweilige Kultur angezeigt. Beachten Sie, dass die zwei Kulturen verschiedene Dezimaltrennzeichen beim Temperaturwert verwenden. Beachten Sie außerdem, dass im Beispiel die benutzerdefinierte Datums- und Uhrzeitformatzeichenfolge "MMMM" verwendet wird, um den vollständigen Monatsnamen anzuzeigen, und dass in der Ergebniszeichenfolge ausreichend Platz für den Monatsnamen zugeordnet wird, indem die Länge des längsten Monatsnamens im DateTimeFormatInfo.MonthNames-Array ermittelt wird.

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

Beibehalten numerischer Werte

Numerische Daten sollten niemals in einem kulturabhängigen Format beibehalten werden. Dies ist ein häufiger Programmierfehler, der zu beschädigten Daten oder einer Laufzeitausnahme führt. Im folgenden Beispiel werden zehn zufällige Gleitkommazahlen generiert und anschließend unter Verwendung der Formatierungskonventionen der Kultur Englisch (USA) als Zeichenfolgen serialisiert. Wenn die Daten mit den Konventionen der Kultur Englisch (USA) abgerufen und analysiert werden, können sie erfolgreich wiederhergestellt werden. Wenn jedoch versucht wird, sie mit den Konventionen der Kultur Französisch (Frankreich) abzurufen und zu analysieren, kann keine der Zahlen analysiert werden, da die Kulturen unterschiedliche Dezimaltrennzeichen verwenden.

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'

Um dieses Problem zu vermeiden, können Sie eine der folgenden Methoden verwenden:

  • Speichern und analysieren Sie die Zeichenfolgendarstellung der Zahl, indem Sie eine benutzerdefinierte Formatzeichenfolge verwenden, die von der Kultur des Benutzers unabhängig ist.
  • Speichern Sie die Zahl als Zeichenfolge, indem Sie die Formatierungskonventionen der invarianten Kultur verwenden, die von der CultureInfo.InvariantCulture-Eigenschaft zurückgegeben wird.

Das Serialisieren von Währungen ist ein Sonderfall. Da ein Währungswert von der Währungseinheit abhängig ist, in der er ausgedrückt wird, ist es wenig sinnvoll, ihn als unabhängigen numerischen Wert zu behandeln. Wenn Sie jedoch einen Währungswert als formatierte Zeichenfolge speichern, die ein Währungssymbol enthält, kann er nicht auf einem System deserialisiert werden, dessen Standardkultur ein anderes Währungssymbol verwendet, wie im folgenden Beispiel gezeigt wird.

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'

Stattdessen sollten Sie den numerischen Wert zusammen mit kulturellen Informationen wie dem Namen der Kultur serialisieren, sodass der Wert und sein Währungssymbol unabhängig von der aktuellen Kultur deserialisiert werden können. Im folgenden Beispiel wird dies erreicht, indem eine CurrencyValue-Struktur mit zwei Membern definiert wird: der Decimal-Wert und der Name der Kultur, der der Wert angehört.

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

Arbeiten mit kulturspezifischen Einstellungen

In .NET stellt die Klasse CultureInfo eine bestimmte Kultur oder Region dar. Einige zugehörige Eigenschaften geben Objekte zurück, die spezielle Informationen über Aspekte einer Kultur bereitstellen:

Treffen Sie generell keine Annahmen über die Werte von bestimmten CultureInfo-Eigenschaften und deren verknüpfte Objekte. Stattdessen sollten Sie aus folgenden Gründen davon ausgehen, dass kulturspezifische Daten Änderungen unterliegen:

  • Einzelne Eigenschaftswerte unterliegen im Laufe der Zeit Änderungen und Revisionen, da Daten korrigiert werden, bessere Daten verfügbar werden oder die kulturspezifischen Konventionen sich ändern.

  • Einzelne Eigenschaftswerte können zwischen Versionen von .NET oder Betriebssystemversionen variieren.

  • .NET unterstützt Ersatzkulturen. Dadurch ist es möglich, eine neue benutzerdefinierte Kultur zu definieren, die bestehende Standardkulturen ergänzt oder vollständig ersetzt.

  • In Windows-Systemen kann der Benutzer kulturspezifische Einstellungen über die App Region and Language (Region und Sprache) in der Systemsteuerung anpassen. Wenn Sie ein CultureInfo-Objekt instanziieren, können Sie festlegen, ob es diese Benutzeranpassungen widergespiegelt, indem Sie den CultureInfo(String, Boolean)-Konstruktor aufrufen. In der Regel sollten Sie bei Endbenutzer-Apps die Benutzereinstellungen berücksichtigen, sodass der Benutzer die Daten in einem Format angezeigt bekommt, das dieser erwartet.

Siehe auch