Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die Globalisierung umfasst das Entwerfen und Entwickeln einer weltweit einsetzbaren App, die lokalisierte Schnittstellen und regionale Daten für Benutzer in mehreren Kulturen unterstützt. Bevor Sie mit der Entwurfsphase beginnen, sollten Sie bestimmen, welche Kulturen Ihre App unterstützt. Obwohl eine App auf eine einzelne Kultur oder Region als Standard ausgerichtet ist, können Sie sie entwerfen und schreiben, damit sie problemlos auf Benutzer in anderen Kulturen oder Regionen erweitert werden kann.
Als Entwickler haben wir alle Annahmen über Benutzeroberflächen und Daten, die von unseren Kulturen gebildet werden. Beispiel: Für einen englischsprachigen Entwickler in den USA ist die Serialisierung von Datums- und Uhrzeitdaten als Zeichenfolge im Format MM/dd/yyyy hh:mm:ss
vollkommen angemessen. Das Deserialisieren dieser Zeichenfolge auf einem System in einer anderen Kultur wird jedoch wahrscheinlich eine FormatException Ausnahme auslösen oder ungenaue Daten erzeugen. Die Globalisierung ermöglicht es uns, solche kulturspezifischen Annahmen zu identifizieren und sicherzustellen, dass sie sich nicht auf das Design oder den Code unserer App auswirken.
In diesem Artikel werden einige der wichtigsten Probleme erläutert, die Sie berücksichtigen sollten, und die bewährten Methoden, die Sie beim Behandeln von Zeichenfolgen, Datums- und Uhrzeitwerten und numerischen Werten in einer globalisierten App befolgen können.
Streichinstrumente
Die Behandlung von Zeichen und Zeichenfolgen ist ein zentraler Schwerpunkt der Globalisierung, da jede Kultur oder Region unterschiedliche Zeichen und Zeichensätze verwenden und sie anders sortieren kann. 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 fast jedes Zeichen in jedem Zeichensatz, der weltweit verwendet wird.
Viele Anwendungen und Betriebssysteme, einschließlich des Windows-Betriebssystems, können auch Codeseiten verwenden, um Zeichensätze darzustellen. Codeseiten enthalten in der Regel die Standard-ASCII-Werte von 0x00 bis 0x7F und ordnen andere Zeichen den verbleibenden Werten von 0x80 bis 0xFF zu. Die Interpretation von Werten von 0x80 bis 0xFF hängt von der jeweiligen Codeseite ab. Aus diesem Gründen sollten Sie die Verwendung von Codeseiten in einer globalisierten App vermeiden, wenn möglich.
Das folgende Beispiel veranschaulicht die Gefahren der Interpretation von Codeseitendaten, wenn sich die Standardcodeseite eines Systems von der Codeseite unterscheidet, auf der die Daten gespeichert wurden. (Um dieses Szenario zu simulieren, gibt das Beispiel explizit verschiedene Codeseiten an.) Zunächst definiert das Beispiel ein Array, das aus den Großbuchstaben des griechischen Alphabets besteht. Sie codiert sie in einem Bytearray mithilfe der Codepage 737 (auch als MS-DOS Griechisch bezeichnet) und speichert das Bytearray in einer Datei. Wenn die Datei abgerufen und das Bytearray mithilfe der Codepage 737 decodiert wird, werden die ursprünglichen Zeichen wiederhergestellt. Wenn die Datei jedoch abgerufen und das Bytearray mithilfe der Codepage 1252 decodiert wird (oder Windows-1252, das Zeichen im lateinischen Alphabet darstellt), 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:
' ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
' €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—
Die Verwendung von Unicode stellt sicher, dass dieselben Codeeinheiten immer den gleichen Zeichen zugeordnet sind und dass dieselben Zeichen immer den gleichen Bytearrays zugeordnet werden.
Verwenden von Ressourcendateien
Auch wenn Sie eine App entwickeln, die auf eine einzelne Kultur oder Region ausgerichtet ist, sollten Sie Ressourcendateien verwenden, um Zeichenfolgen und andere Ressourcen zu speichern, die auf der Benutzeroberfläche angezeigt werden. Sie sollten sie niemals direkt zu Ihrem Code hinzufügen. Die Verwendung von Ressourcendateien hat eine Reihe von Vorteilen:
- Alle Zeichenfolgen befinden sich an einem einzigen Ort. Sie müssen nicht im gesamten Quellcode suchen, um Zeichenfolgen zu identifizieren, die für eine bestimmte Sprache oder Kultur geändert werden sollen.
- Es ist nicht erforderlich, Zeichenfolgen zu duplizieren. Entwickler, die keine Ressourcendateien verwenden, definieren häufig dieselbe Zeichenfolge in mehreren Quellcodedateien. Diese Duplizierung erhöht die Wahrscheinlichkeit, dass eine oder mehrere Instanzen übersehen werden, wenn eine Zeichenfolge geändert wird.
- Sie können Nicht-Zeichenfolgenressourcen, z. B. Bilder oder Binärdaten, in die Ressourcendatei einschließen, anstatt sie in einer separaten eigenständigen Datei zu speichern, sodass sie problemlos abgerufen werden können.
Die Verwendung von Ressourcendateien hat besondere Vorteile, wenn Sie eine lokalisierte App erstellen. Wenn Sie Ressourcen in Satellitenassemblys bereitstellen, wählt die Common Language Runtime (CLR) automatisch eine kulturgerechte Ressource basierend auf der aktuellen UI-Kultur des Benutzers aus, wie von der CultureInfo.CurrentUICulture-Eigenschaft definiert. Solange Sie eine geeignete kulturspezifische Ressource bereitstellen und ein ResourceManager Objekt ordnungsgemäß instanziieren oder eine stark typierte Ressourcenklasse verwenden, verarbeitet die Laufzeit die Details zum Abrufen der entsprechenden Ressourcen.
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 Package and Deploy resources.
Suchen und Vergleichen von Zeichenfolgen
Wenn möglich, sollten Sie Zeichenfolgen als ganze Zeichenfolgen behandeln, anstatt sie als Eine Reihe einzelner Zeichen zu behandeln. Dies ist besonders wichtig, wenn Sie nach Teilzeichenfolgen sortieren oder suchen, um Probleme im Zusammenhang mit der Analyse kombinierter Zeichen zu vermeiden.
Tipp
Sie können die StringInfo Klasse verwenden, um mit den Textelementen und nicht mit den einzelnen Zeichen in einer Zeichenfolge zu arbeiten.
Bei Zeichenfolgensuchen und Vergleichen besteht ein häufiger Fehler darin, die Zeichenfolge als Eine Sammlung von Zeichen zu behandeln, die jeweils durch ein Char Objekt dargestellt werden. Tatsächlich kann ein einzelnes Zeichen durch ein, zwei oder mehr Char Objekte gebildet werden. Solche Zeichen werden am häufigsten in Zeichenfolgen aus Kulturen gefunden, deren Alphabete aus Zeichen außerhalb des Unicode Basic Latin Zeichenbereichs bestehen (U+0021 bis U+007E). 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 durch zwei Char Objekte, U+0041 und U+0300 dargestellt. Der Beispielcode ruft die Überladungen String.IndexOf(Char) und String.IndexOf(String) auf, um die Position dieses Zeichens in der Zeichenfolgeninstanz zu finden, aber sie geben unterschiedliche Ergebnisse zurück. Der erste Methodenaufruf weist ein Char Argument auf; er führt einen Ordinalvergleich aus und kann daher keine Übereinstimmung finden. Der zweite Aufruf weist ein String Argument auf; er führt einen kultursensitiven Vergleich aus 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: {composite.IndexOf('\u00C0')}");
Console.WriteLine($"Comparing using String: {composite.IndexOf("\u00C0")}");
}
}
// The example displays the following output:
// Comparing using Char: -1
// Comparing using String: 0
Imports System.Globalization
Imports System.Threading
Module Example17
Public Sub Main17()
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("pl-PL")
Dim composite As String = ChrW(&H41) + ChrW(&H300)
Console.WriteLine("Comparing using Char: {0}", composite.IndexOf(ChrW(&HC0)))
Console.WriteLine("Comparing using String: {0}", composite.IndexOf(ChrW(&HC0).ToString()))
End Sub
End Module
' The example displays the following output:
' Comparing using Char: -1
' Comparing using String: 0
Sie können einige der Mehrdeutigkeiten dieses Beispiels vermeiden (Aufrufe von zwei ähnlichen Überladungen einer Methode, die unterschiedliche Ergebnisse zurückgibt), indem Sie eine Überladung aufrufen, die einen StringComparison Parameter enthält, z. B. die String.IndexOf(String, StringComparison) Methode.String.LastIndexOf(String, StringComparison)
Suchvorgänge sind jedoch nicht immer kulturempfindlich. Wenn der Zweck der Suche darin besteht, eine Sicherheitsentscheidung zu treffen oder den Zugriff auf eine Ressource zu erlauben oder zu verweigern, sollte ein ordinaler Vergleich durchgeführt werden, wie im nächsten Abschnitt besprochen wird.
Testen von Zeichenfolgen auf Gleichheit
Wenn Sie zwei Zeichenfolgen auf Gleichheit testen möchten, anstatt zu bestimmen, wie sie in der Sortierreihenfolge verglichen werden, verwenden Sie die String.Equals Methode anstelle einer Zeichenfolgenvergleichsmethode wie String.Compare oder CompareInfo.Compare.
Vergleiche für Gleichheit werden in der Regel durchgeführt, um bedingt auf einige Ressourcen zuzugreifen. Sie können z. B. einen Vergleich zur Gleichheit durchführen, um ein Kennwort zu überprüfen oder zu bestätigen, dass eine Datei vorhanden ist. Solche nicht sprachlichen Vergleiche sollten immer ordinal und nicht kulturempfindlich sein. Im Allgemeinen sollten Sie die Instanzmethode String.Equals(String, StringComparison) oder die statische String.Equals(String, String, StringComparison) Methode mit einem Wert StringComparison.Ordinal für Zeichenfolgen wie Kennwörter und einen Wert StringComparison.OrdinalIgnoreCase für Zeichenfolgen wie Dateinamen oder URIs aufrufen.
Vergleiche für Gleichheit umfassen manchmal Such- 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 Vergleichs nicht linguistisch ist, sollte die Suche auch ordinal und nicht kultursensitiv sein.
Das folgende Beispiel veranschaulicht die Gefahr einer kultursensiblen Suche nach nicht sprachlichen Daten. Die AccessesFileSystem
Methode wurde entwickelt, um den Dateisystemzugriff für URIs zu verbieten, 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, entweder mit "FILE:" oder "file:" beginnen kann, ist die implizite Annahme, dass "i" (U+0069) immer das Kleinbuchstabenäquivalent von "I" (U+0049) ist. In Türkisch und Aserbaidschan ist die Großbuchstabenversion von "i" jedoch "İ" (U+0130). Aufgrund dieser Diskrepanz ermöglicht der kultursensitive Vergleich den Dateisystemzugriff, wenn es verboten sein sollte.
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.
Anordnen und Sortieren von Zeichenfolgen
In der Regel sollten sortierte Zeichenfolgen, die auf der Benutzeroberfläche angezeigt werden sollen, basierend auf der Kultur sortiert werden. In den meisten Fällen werden solche Zeichenfolgenvergleiche implizit von .NET behandelt, wenn Sie eine Methode aufrufen, die Zeichenfolgen sortiert, wie Array.Sort oder List<T>.Sort. Standardmäßig werden Zeichenfolgen mithilfe der Sortierkonventionen der aktuellen Kultur sortiert. Im folgenden Beispiel wird der Unterschied veranschaulicht, wenn ein Array von Zeichenfolgen anhand der Konventionen der englischen Kultur (USA) und der schwedischen Kultur (Schweden) sortiert wird.
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. Kultursensitive Zeichenfolgenvergleiche, die die String.Compare Methodenüberladungen verwenden, verwenden auch das CompareInfo Objekt.
.NET verwendet Tabellen, um kultursensitive Sortierungen für Zeichenfolgendaten durchzuführen. Der Inhalt dieser Tabellen, die Daten zu Sortiergewichtungen und Zeichenfolgennormalisierung enthalten, wird durch die Version des Unicode-Standards bestimmt, der von einer bestimmten Version von .NET implementiert wird. In der folgenden Tabelle sind die Von den angegebenen Versionen von .NET implementierten Unicode-Versionen aufgeführt. Diese Liste der unterstützten Unicode-Versionen gilt nur für den Zeichenvergleich und die Sortierung; sie gilt nicht für die Klassifizierung von Unicode-Zeichen nach Kategorie. Weitere Informationen finden Sie im Abschnitt "Strings and The Unicode Standard" im String Artikel.
.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 höher | Unicode 6.3.0 |
.NET Core und .NET 5+ | Hängt von der Vom zugrunde liegenden Betriebssystem unterstützten Version des Unicode-Standards ab. |
Ab .NET Framework 4.5 und in allen Versionen von .NET Core und .NET 5+ hängt der Zeichenfolgenvergleich und die Sortierung vom Betriebssystem ab. .NET Framework 4.5 und höher unter Windows 7 ruft Daten aus eigenen Tabellen ab, die Unicode 5.0 implementieren. .NET Framework 4.5 und höher unter Windows 8 und höher ruft Daten aus Betriebssystemtabellen ab, die Unicode 6.3 implementieren. Auf .NET Core und .NET 5+ hängt die unterstützte Version von Unicode vom zugrunde liegenden Betriebssystem ab. Wenn Sie kulturabhängige sortierte Daten serialisieren, können Sie mithilfe der SortVersion Klasse ermitteln, wann die serialisierten Daten sortiert werden müssen, damit sie mit .NET und der Sortierreihenfolge des Betriebssystems konsistent ist. Ein Beispiel finden Sie im SortVersion Klassenthema.
Wenn Ihre App umfangreiche kulturspezifische Sortierungen von Zeichenfolgendaten durchführt, können Sie mit der SortKey Klasse arbeiten, um Zeichenfolgen zu vergleichen. Ein Sortierschlüssel spiegelt die kulturspezifischen Sortiergewichte wider, einschließlich der alphabetischen, der Großschreibungs- und der diakritischen Gewichtungen einer bestimmten Zeichenkette. Da Vergleiche mit Sortierschlüsseln binär sind, sind sie schneller als Vergleiche, die ein CompareInfo Objekt implizit oder explizit verwenden. Sie erstellen einen kulturspezifischen 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, definiert sie eine System.Collections.Generic.IComparer<T> Implementierung, die Sortierschlüssel vergleicht, die sie instanziiert und an die Array.Sort<T>(T[], IComparer<T>) Methode übergibt.
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
Wenn möglich, vermeiden Sie die Verwendung zusammengesetzter Zeichenketten, die zur Laufzeit aus verknüpften Phrasen erstellt werden. Zusammengesetzte Zeichenfolgen sind schwierig zu lokalisieren, da sie häufig eine Grammatikreihenfolge in der Ursprünglichsprache der App annehmen, die nicht für andere lokalisierte Sprachen gilt.
Behandeln von Datums- und Uhrzeitangaben
Wie Sie Datums- und Uhrzeitwerte behandeln, hängt davon ab, ob sie auf der Benutzeroberfläche angezeigt oder beibehalten werden. In diesem Abschnitt werden beide Verwendungen untersucht. Außerdem wird erläutert, wie Sie Zeitzonenunterschiede und arithmetische Vorgänge beim Arbeiten mit Datums- und Uhrzeitangaben behandeln können.
Datums- und Uhrzeitangaben anzeigen
In der Regel sollten Sie, wenn Datums- und Uhrzeitangaben auf der Benutzeroberfläche angezeigt werden, die Formatierungskonventionen der Kultur des Benutzers verwenden, die durch die CultureInfo.CurrentCulture Eigenschaft und das von DateTimeFormatInfo der CultureInfo.CurrentCulture.DateTimeFormat
Eigenschaft zurückgegebene Objekt definiert wird. Die Formatierungskonventionen der aktuellen Kultur werden automatisch verwendet, wenn Sie ein Datum mithilfe einer der folgenden Methoden formatieren:
- Die parameterlose DateTime.ToString() Methode.
- Die DateTime.ToString(String) Methode, die eine Formatzeichenfolge enthält.
- Die parameterlose DateTimeOffset.ToString() Methode.
- Der DateTimeOffset.ToString(String), der eine Formatzeichenfolge enthält.
- Die Funktion zur zusammengesetzten Formatierung, wenn sie mit Datumsangaben verwendet wird.
Im folgenden Beispiel werden die Daten für Sonnenaufgang und Sonnenuntergang zweimal für den 11. Oktober 2012 angezeigt. Zunächst wird die aktuelle Kultur auf Kroatisch (Kroatien) und dann auf Englisch (Vereinigtes Königreich) gesetzt. In jedem Fall werden die Datums- und Uhrzeitangaben im Format angezeigt, das für diese Kultur geeignet ist.
using System;
using System.Globalization;
using System.Threading;
public class Example3
{
static DateTime[] dates = { new DateTime(2012, 10, 11, 7, 06, 0),
new DateTime(2012, 10, 11, 18, 19, 0) };
public static void Main3()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hr-HR");
ShowDayInfo();
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
ShowDayInfo();
}
private static void ShowDayInfo()
{
Console.WriteLine($"Date: {dates[0]:D}");
Console.WriteLine($" Sunrise: {dates[0]:T}");
Console.WriteLine($" Sunset: {dates[1]:T}");
}
}
// The example displays the following output:
// Date: 11. listopada 2012.
// Sunrise: 7:06:00
// Sunset: 18:19:00
//
// Date: 11 October 2012
// Sunrise: 07:06:00
// Sunset: 18:19:00
Imports System.Globalization
Imports System.Threading
Module Example3
Dim dates() As Date = {New Date(2012, 10, 11, 7, 6, 0),
New Date(2012, 10, 11, 18, 19, 0)}
Public Sub Main3()
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hr-HR")
ShowDayInfo()
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
ShowDayInfo()
End Sub
Private Sub ShowDayInfo()
Console.WriteLine("Date: {0:D}", dates(0))
Console.WriteLine(" Sunrise: {0:T}", dates(0))
Console.WriteLine(" Sunset: {0:T}", dates(1))
End Sub
End Module
' The example displays the following output:
' Date: 11. listopada 2012.
' Sunrise: 7:06:00
' Sunset: 18:19:00
'
' Date: 11 October 2012
' Sunrise: 07:06:00
' Sunset: 18:19:00
Datums- und Uhrzeitangaben beibehalten
Sie sollten Datums- und Uhrzeitdaten niemals in einem Format speichern, das je nach Kultur variieren kann. Dies ist ein gängiger Programmierfehler, der zu beschädigten Daten oder einer Laufzeit-Ausnahme führt. Im folgenden Beispiel werden zwei Datumsangaben, den 9. Januar 2013 und den 18. August 2013, als Zeichenfolgen mithilfe der Formatierungskonventionen der englischen Kultur (USA) serialisiert. Wenn die Daten mithilfe der Konventionen der englischen Kultur (USA) abgerufen und analysiert werden, wird sie erfolgreich wiederhergestellt. Wenn sie jedoch mithilfe der Konventionen der englischen Kultur (Vereinigtes Königreich) abgerufen und analysiert wird, wird das erste Datum falsch als 1. September interpretiert, und die zweite kann nicht analysiert werden, da der gregorianische Kalender nicht über einen achtzehnten Monat verfügt.
using System;
using System.IO;
using System.Globalization;
using System.Threading;
public class Example4
{
public static void Main4()
{
// Persist two dates as strings.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
DateTime[] dates = { new DateTime(2013, 1, 9),
new DateTime(2013, 8, 18) };
StreamWriter sw = new StreamWriter("dateData.dat");
sw.Write("{0:d}|{1:d}", dates[0], dates[1]);
sw.Close();
// Read the persisted data.
StreamReader sr = new StreamReader("dateData.dat");
string dateData = sr.ReadToEnd();
sr.Close();
string[] dateStrings = dateData.Split('|');
// Restore and display the data using the conventions of the en-US culture.
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var dateStr in dateStrings)
{
DateTime restoredDate;
if (DateTime.TryParse(dateStr, out restoredDate))
Console.WriteLine($"The date is {restoredDate:D}");
else
Console.WriteLine($"ERROR: Unable to parse {dateStr}");
}
Console.WriteLine();
// Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var dateStr in dateStrings)
{
DateTime restoredDate;
if (DateTime.TryParse(dateStr, out restoredDate))
Console.WriteLine($"The date is {restoredDate:D}");
else
Console.WriteLine($"ERROR: Unable to parse {dateStr}");
}
}
}
// The example displays the following output:
// Current Culture: English (United States)
// The date is Wednesday, January 09, 2013
// The date is Sunday, August 18, 2013
//
// Current Culture: English (United Kingdom)
// The date is 01 September 2013
// ERROR: Unable to parse 8/18/2013
Imports System.Globalization
Imports System.IO
Imports System.Threading
Module Example4
Public Sub Main4()
' Persist two dates as strings.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
Dim dates() As DateTime = {New DateTime(2013, 1, 9),
New DateTime(2013, 8, 18)}
Dim sw As New StreamWriter("dateData.dat")
sw.Write("{0:d}|{1:d}", dates(0), dates(1))
sw.Close()
' Read the persisted data.
Dim sr As New StreamReader("dateData.dat")
Dim dateData As String = sr.ReadToEnd()
sr.Close()
Dim dateStrings() As String = dateData.Split("|"c)
' Restore and display the data using the conventions of the en-US culture.
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each dateStr In dateStrings
Dim restoredDate As Date
If Date.TryParse(dateStr, restoredDate) Then
Console.WriteLine("The date is {0:D}", restoredDate)
Else
Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
End If
Next
Console.WriteLine()
' Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each dateStr In dateStrings
Dim restoredDate As Date
If Date.TryParse(dateStr, restoredDate) Then
Console.WriteLine("The date is {0:D}", restoredDate)
Else
Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
End If
Next
End Sub
End Module
' The example displays the following output:
' Current Culture: English (United States)
' The date is Wednesday, January 09, 2013
' The date is Sunday, August 18, 2013
'
' Current Culture: English (United Kingdom)
' The date is 01 September 2013
' ERROR: Unable to parse 8/18/2013
Sie können dieses Problem auf drei Arten vermeiden:
- Serialisieren Sie das Datum und die Uhrzeit im Binärformat und nicht als Zeichenfolge.
- Speichern und analysieren Sie die Zeichenfolgendarstellung des Datums und der Uhrzeit mithilfe einer benutzerdefinierten Formatzeichenfolge, die unabhängig von der Kultur des Benutzers identisch ist.
- Speichern Sie die Zeichenfolge mithilfe der Formatierungskonventionen der invarianten Kultur.
Das folgende Beispiel veranschaulicht den letzten Ansatz. 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: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var dateStr in dateStrings)
{
DateTime restoredDate;
if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, out restoredDate))
Console.WriteLine($"The date is {restoredDate:D}");
else
Console.WriteLine($"ERROR: Unable to parse {dateStr}");
}
Console.WriteLine();
// Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var dateStr in dateStrings)
{
DateTime restoredDate;
if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, out restoredDate))
Console.WriteLine($"The date is {restoredDate:D}");
else
Console.WriteLine($"ERROR: Unable to parse {dateStr}");
}
}
}
// The example displays the following output:
// Current Culture: English (United States)
// The date is Wednesday, January 09, 2013
// The date is Sunday, August 18, 2013
//
// Current Culture: English (United Kingdom)
// The date is 09 January 2013
// The date is 18 August 2013
Imports System.Globalization
Imports System.IO
Imports System.Threading
Module Example5
Public Sub Main5()
' Persist two dates as strings.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
Dim dates() As DateTime = {New DateTime(2013, 1, 9),
New DateTime(2013, 8, 18)}
Dim sw As New StreamWriter("dateData.dat")
sw.Write(String.Format(CultureInfo.InvariantCulture,
"{0:d}|{1:d}", dates(0), dates(1)))
sw.Close()
' Read the persisted data.
Dim sr As New StreamReader("dateData.dat")
Dim dateData As String = sr.ReadToEnd()
sr.Close()
Dim dateStrings() As String = dateData.Split("|"c)
' Restore and display the data using the conventions of the en-US culture.
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each dateStr In dateStrings
Dim restoredDate As Date
If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, restoredDate) Then
Console.WriteLine("The date is {0:D}", restoredDate)
Else
Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
End If
Next
Console.WriteLine()
' Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each dateStr In dateStrings
Dim restoredDate As Date
If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, restoredDate) Then
Console.WriteLine("The date is {0:D}", restoredDate)
Else
Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
End If
Next
End Sub
End Module
' The example displays the following output:
' Current Culture: English (United States)
' The date is Wednesday, January 09, 2013
' The date is Sunday, August 18, 2013
'
' Current Culture: English (United Kingdom)
' The date is 09 January 2013
' The date is 18 August 2013
Serialisierung und Zeitzonenbewusstsein
Ein Datums- und Uhrzeitwert kann mehrere Interpretationen aufweisen, von einer allgemeinen Zeit ("Die Stores sind am 2. Januar 2013 um 9:00 Uhr geöffnet") bis zu einem bestimmten Zeitpunkt ("Geburtsdatum: 2. Januar 2013 6:32:00 Uhr A.M."). Wenn ein Zeitwert einen bestimmten Zeitpunkt darstellt und Sie ihn aus einem serialisierten Wert wiederherstellen, sollten Sie sicherstellen, dass er unabhängig vom geografischen Standort oder der Zeitzone des Benutzers denselben Zeitpunkt darstellt.
Das folgende Beispiel veranschaulicht dieses Problem. Sie speichert einen einzelnen lokalen Datums- und Uhrzeitwert als Zeichenfolge in drei Standardformaten:
- „G“ für generelles Datum, lange Zeit.
- "s" für sortierbares Datum/Uhrzeit.
- "o" für Roundtrip-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($"'{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 derselben Zeitzone wie das System wiederhergestellt werden, in dem sie serialisiert wurde, spiegeln die deserialisierten Datums- und Uhrzeitwerte den ursprünglichen Wert genau 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 in einem System in einer anderen Zeitzone wiederherstellen, behält jedoch nur der Datums- und Uhrzeitwert, der mit der Standardformatzeichenfolge "o" (Roundtrip) formatiert wurde, Zeitzoneninformationen und stellt daher die gleiche Zeit dar. Dies ist die Ausgabe, wenn die Datums- und Uhrzeitdaten auf einem System in der Zeitzone Romanze Standard wiederhergestellt werden:
'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
Wenn Sie einen Datums- und Uhrzeitwert genau wiedergeben möchten, der einen einzelnen Zeitmoment darstellt, unabhängig von der Zeitzone des Systems, in der die Daten deserialisiert werden, können Sie eine der folgenden Aktionen ausführen:
- Speichern Sie den Wert als Zeichenfolge mithilfe der Standardformatzeichenfolge "o" (Roundtrip). Deserialisieren Sie ihn anschließend auf dem Zielsystem.
- Konvertieren Sie sie in UTC, und speichern Sie sie als Zeichenfolge mithilfe der Standardformatzeichenfolge "r" (RFC1123). Deserialisieren Sie sie dann im Zielsystem, und konvertieren Sie sie in lokale Zeit.
- Konvertieren Sie sie in UTC, und speichern Sie sie als Zeichenfolge mithilfe der Standardformatzeichenfolge "u" (universal sortable). Deserialisieren Sie sie dann im Zielsystem, und konvertieren Sie sie in lokale Zeit.
Im folgenden Beispiel werden die einzelnen Techniken veranschaulicht.
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 Pazifischen Standardzeitzone serialisiert und auf einem System in der Romanischen Standardzeitzone deserialisiert werden, zeigt das Beispiel die folgende Ausgabe an:
'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 Zeiten zwischen Zeitzonen.
Ausführen von Datums- und Uhrzeitarithmetik
Sowohl die DateTime- als auch die DateTimeOffset-Typen unterstützen arithmetische Vorgänge. Sie können die Differenz zwischen zwei Datumswerten berechnen, oder Sie können bestimmte Zeitintervalle zu oder von einem Datumswert addieren oder subtrahieren. Arithmetische Vorgänge für Datums- und Uhrzeitwerte berücksichtigen jedoch keine Zeitzonen- und Zeitzonenanpassungsregeln. Daher können Datums- und Uhrzeitarithmetik für Werte, die Momente in der Zeit darstellen, ungenaue Ergebnisse zurückgeben.
Beispielsweise erfolgt der Übergang von Pacific Standard Time zu Pacific Daylight Time am zweiten Sonntag vom März, der 10. März für das Jahr 2013 ist. 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($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
}
}
// The example displays the following output:
// 3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 10:30 AM
Module Example7
Public Sub Main7()
Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
DateTimeKind.Local)
Dim interval As New TimeSpan(48, 0, 0)
Dim date2 As Date = date1 + interval
Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
date1, interval.TotalHours, date2)
End Sub
End Module
' The example displays the following output:
' 3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 10:30 AM
Führen Sie die folgenden Schritte aus, um sicherzustellen, dass ein arithmetischer Vorgang mit Datums- und Uhrzeitwerten genaue Ergebnisse erzeugt:
- Konvertieren Sie die Uhrzeit in der Quellzeitzone in UTC.
- Führen Sie die arithmetische Operation aus.
- Wenn das Ergebnis ein Datums- und Uhrzeitwert ist, konvertieren Sie es von UTC in die Uhrzeit in der Quellzeitzone.
Das folgende Beispiel ähnelt dem vorherigen Beispiel, es sei denn, es folgt diesen drei Schritten, um 48 Stunden bis zum 9. März 2013 um 10:30 Uhr ordnungsgemäß hinzuzufügen.
using System;
public class Example8
{
public static void Main8()
{
TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
DateTime date1 = DateTime.SpecifyKind(new DateTime(2013, 3, 9, 10, 30, 0),
DateTimeKind.Local);
DateTime utc1 = date1.ToUniversalTime();
TimeSpan interval = new TimeSpan(48, 0, 0);
DateTime utc2 = utc1 + interval;
DateTime date2 = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst);
Console.WriteLine($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
}
}
// The example displays the following output:
// 3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM
Module Example8
Public Sub Main8()
Dim pst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")
Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
DateTimeKind.Local)
Dim utc1 As Date = date1.ToUniversalTime()
Dim interval As New TimeSpan(48, 0, 0)
Dim utc2 As Date = utc1 + interval
Dim date2 As Date = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst)
Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
date1, interval.TotalHours, date2)
End Sub
End Module
' The example displays the following output:
' 3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM
Weitere Informationen finden Sie unter Ausführen von arithmetischen Vorgängen mit Datums- und Uhrzeitangaben.
Verwenden von Kultursensiblen Namen für Datumselemente
Ihre App muss möglicherweise den Namen des Monats oder den Wochentag anzeigen. Dazu ist Code wie der folgende häufig verwendet.
using System;
public class Example12
{
public static void Main12()
{
DateTime midYear = new DateTime(2013, 7, 1);
Console.WriteLine($"{midYear:d} is a {GetDayName(midYear)}.");
}
private static string GetDayName(DateTime date)
{
return date.DayOfWeek.ToString("G");
}
}
// The example displays the following output:
// 7/1/2013 is a Monday.
Module Example12
Public Sub Main12()
Dim midYear As Date = #07/01/2013#
Console.WriteLine("{0:d} is a {1}.", midYear, GetDayName(midYear))
End Sub
Private Function GetDayName(dat As Date) As String
Return dat.DayOfWeek.ToString("G")
End Function
End Module
' The example displays the following output:
' 7/1/2013 is a Monday.
Dieser Code gibt jedoch immer die Namen der Wochentage in Englisch zurück. Code, der den Namen des Monats extrahiert, ist oft noch unflexibler. Es wird häufig angenommen, dass ein Zwölfmonatskalender mit Monatsnamen in einer spezifischen Sprache verwendet wird.
Wenn Sie benutzerdefinierte Datums- und Uhrzeitformatzeichenfolgen oder die Eigenschaften des DateTimeFormatInfo Objekts verwenden, ist es einfach, Zeichenfolgen zu extrahieren, die die Namen von Tagen der Woche oder Monate in der Kultur des Benutzers widerspiegeln, wie das folgende 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($"{midYear:d} is a {DateUtilities.GetDayName(midYear)}.");
Console.WriteLine($"{midYear:d} is a {DateUtilities.GetDayName((int)midYear.DayOfWeek)}.");
Console.WriteLine($"{midYear:d} is in {DateUtilities.GetMonthName(midYear)}.");
Console.WriteLine($"{midYear:d} is in {DateUtilities.GetMonthName(midYear.Month)}.");
}
}
public static class DateUtilities
{
public static string GetDayName(int dayOfWeek)
{
if (dayOfWeek < 0 | dayOfWeek > DateTimeFormatInfo.CurrentInfo.DayNames.Length)
return String.Empty;
else
return DateTimeFormatInfo.CurrentInfo.DayNames[dayOfWeek];
}
public static string GetDayName(DateTime date)
{
return date.ToString("dddd");
}
public static string GetMonthName(int month)
{
if (month < 1 | month > DateTimeFormatInfo.CurrentInfo.MonthNames.Length - 1)
return String.Empty;
else
return DateTimeFormatInfo.CurrentInfo.MonthNames[month - 1];
}
public static string GetMonthName(DateTime date)
{
return date.ToString("MMMM");
}
}
// The example displays the following output:
// 01/07/2013 is a lundi.
// 01/07/2013 is a lundi.
// 01/07/2013 is in juillet.
// 01/07/2013 is in juillet.
Imports System.Globalization
Imports System.Threading
Module Example13
Public Sub Main13()
' Set the current culture to French (France).
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
Dim midYear As Date = #07/01/2013#
Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear))
Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear.DayOfWeek))
Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear))
Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear.Month))
End Sub
End Module
Public Class DateUtilities
Public Shared Function GetDayName(dayOfWeek As Integer) As String
If dayOfWeek < 0 Or dayOfWeek > DateTimeFormatInfo.CurrentInfo.DayNames.Length Then
Return String.Empty
Else
Return DateTimeFormatInfo.CurrentInfo.DayNames(dayOfWeek)
End If
End Function
Public Shared Function GetDayName(dat As Date) As String
Return dat.ToString("dddd")
End Function
Public Shared Function GetMonthName(month As Integer) As String
If month < 1 Or month > DateTimeFormatInfo.CurrentInfo.MonthNames.Length - 1 Then
Return String.Empty
Else
Return DateTimeFormatInfo.CurrentInfo.MonthNames(month - 1)
End If
End Function
Public Shared Function GetMonthName(dat As Date) As String
Return dat.ToString("MMMM")
End Function
End Class
' The example displays the following output:
' 01/07/2013 is a lundi.
' 01/07/2013 is a lundi.
' 01/07/2013 is in juillet.
' 01/07/2013 is in juillet.
Numerische Werte
Die Behandlung von Zahlen hängt davon ab, ob sie auf der Benutzeroberfläche angezeigt oder beibehalten werden. In diesem Abschnitt werden beide Verwendungen untersucht.
Hinweis
Bei Analyse- und Formatierungsvorgängen erkennt .NET nur die lateinischen Grundzeichen 0 bis 9 (U+0030 bis U+0039) als numerische Ziffern.
Numerische Werte anzeigen
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 auf folgende Weise formatieren:
- Verwenden der parameterlosen
ToString
Methode eines beliebigen numerischen Typs. - Verwenden der
ToString(String)
Methode eines beliebigen numerischen Typs, die eine Formatzeichenfolge als Argument enthält. - Verwenden der zusammengesetzten Formatierung mit numerischen Werten.
Im folgenden Beispiel wird die Durchschnittliche Temperatur pro Monat in Paris, Frankreich, angezeigt. Zunächst wird die aktuelle Kultur auf Französisch (Frankreich) festgelegt, bevor die Daten angezeigt werden, und anschließend wird sie auf Englisch (USA) festgelegt. In jedem Fall werden die Monatsnamen und -temperaturen im Format angezeigt, das für diese Kultur geeignet ist. Beachten Sie, dass die beiden Kulturen unterschiedliche Dezimaltrennzeichen im 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 sie den entsprechenden Speicherplatz für den Monatsnamen in der Ergebniszeichenfolge zuweist, indem die Länge des längsten Monatsnamens im DateTimeFormatInfo.MonthNames Array bestimmt 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: {CultureInfo.CurrentCulture.DisplayName}");
// Build the format string dynamically so we allocate enough space for the month name.
string fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM} {1,4}";
for (int ctr = 0; ctr < temperatures.Length; ctr++)
Console.WriteLine(fmtString,
dateForMonth.AddMonths(ctr),
temperatures[ctr]);
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM} {1,4}";
for (int ctr = 0; ctr < temperatures.Length; ctr++)
Console.WriteLine(fmtString,
dateForMonth.AddMonths(ctr),
temperatures[ctr]);
}
private static int GetLongestMonthNameLength()
{
int length = 0;
foreach (var nameOfMonth in DateTimeFormatInfo.CurrentInfo.MonthNames)
if (nameOfMonth.Length > length) length = nameOfMonth.Length;
return length;
}
}
// The example displays the following output:
// Current Culture: French (France)
// janvier 3,4
// février 3,5
// mars 7,6
// avril 10,4
// mai 14,5
// juin 17,2
// juillet 19,9
// août 18,2
// septembre 15,9
// octobre 11,3
// novembre 6,9
// décembre 5,3
//
// Current Culture: English (United States)
// January 3.4
// February 3.5
// March 7.6
// April 10.4
// May 14.5
// June 17.2
// July 19.9
// August 18.2
// September 15.9
// October 11.3
// November 6.9
// December 5.3
Imports System.Globalization
Imports System.Threading
Module Example14
Public Sub Main14()
Dim dateForMonth As Date = #1/1/2013#
Dim temperatures() As Double = {3.4, 3.5, 7.6, 10.4, 14.5, 17.2,
19.9, 18.2, 15.9, 11.3, 6.9, 5.3}
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
Dim fmtString As String = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM} {1,4}"
For ctr = 0 To temperatures.Length - 1
Console.WriteLine(fmtString,
dateForMonth.AddMonths(ctr),
temperatures(ctr))
Next
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
' Build the format string dynamically so we allocate enough space for the month name.
fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM} {1,4}"
For ctr = 0 To temperatures.Length - 1
Console.WriteLine(fmtString,
dateForMonth.AddMonths(ctr),
temperatures(ctr))
Next
End Sub
Private Function GetLongestMonthNameLength() As Integer
Dim length As Integer
For Each nameOfMonth In DateTimeFormatInfo.CurrentInfo.MonthNames
If nameOfMonth.Length > length Then length = nameOfMonth.Length
Next
Return length
End Function
End Module
' The example displays the following output:
' Current Culture: French (France)
' janvier 3,4
' février 3,5
' mars 7,6
' avril 10,4
' mai 14,5
' juin 17,2
' juillet 19,9
' août 18,2
' septembre 15,9
' octobre 11,3
' novembre 6,9
' décembre 5,3
'
' Current Culture: English (United States)
' January 3.4
' February 3.5
' March 7.6
' April 10.4
' May 14.5
' June 17.2
' July 19.9
' August 18.2
' September 15.9
' October 11.3
' November 6.9
' December 5.3
Beibehalten numerischer Werte
Sie sollten keine numerischen Daten in einem kulturspezifischen Format speichern. Dies ist ein gängiger Programmierfehler, der zu beschädigten Daten oder einer Laufzeit-Ausnahme führt. Im folgenden Beispiel werden zehn zufällige Gleitkommazahlen generiert und dann mithilfe der Formatierungskonventionen der Englisch-Kultur (USA) als Zeichenfolgen serialisiert. Wenn die Daten mithilfe der Konventionen der englischen Kultur (USA) abgerufen und analysiert werden, wird sie erfolgreich wiederhergestellt. Wenn sie jedoch mithilfe der Konventionen der französischen Kultur (Frankreich) abgerufen und analysiert wird, kann keines 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: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var numberStr in numberStrings)
{
double restoredNumber;
if (Double.TryParse(numberStr, out restoredNumber))
Console.WriteLine(restoredNumber.ToString("R"));
else
Console.WriteLine($"ERROR: Unable to parse '{numberStr}'");
}
Console.WriteLine();
// Restore and display the data using the conventions of the fr-FR culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var numberStr in numberStrings)
{
double restoredNumber;
if (Double.TryParse(numberStr, out restoredNumber))
Console.WriteLine(restoredNumber.ToString("R"));
else
Console.WriteLine($"ERROR: Unable to parse '{numberStr}'");
}
}
private static double[] GetRandomNumbers(int n)
{
Random rnd = new Random();
double[] numbers = new double[n];
for (int ctr = 0; ctr < n; ctr++)
numbers[ctr] = rnd.NextDouble() * 1000;
return numbers;
}
private static void DisplayRandomNumbers(double[] numbers)
{
for (int ctr = 0; ctr < numbers.Length; ctr++)
Console.WriteLine(numbers[ctr].ToString("R"));
Console.WriteLine();
}
}
// The example displays output like the following:
// 487.0313743534644
// 674.12000879371533
// 498.72077885024288
// 42.3034229512808
// 970.57311049223563
// 531.33717716268131
// 587.82905693530529
// 562.25210175023039
// 600.7711019370571
// 299.46113717717174
//
// Current Culture: English (United States)
// 487.0313743534644
// 674.12000879371533
// 498.72077885024288
// 42.3034229512808
// 970.57311049223563
// 531.33717716268131
// 587.82905693530529
// 562.25210175023039
// 600.7711019370571
// 299.46113717717174
//
// Current Culture: French (France)
// ERROR: Unable to parse '487.0313743534644'
// ERROR: Unable to parse '674.12000879371533'
// ERROR: Unable to parse '498.72077885024288'
// ERROR: Unable to parse '42.3034229512808'
// ERROR: Unable to parse '970.57311049223563'
// ERROR: Unable to parse '531.33717716268131'
// ERROR: Unable to parse '587.82905693530529'
// ERROR: Unable to parse '562.25210175023039'
// ERROR: Unable to parse '600.7711019370571'
// ERROR: Unable to parse '299.46113717717174'
Imports System.Globalization
Imports System.IO
Imports System.Threading
Module Example15
Public Sub Main15()
' Create ten random doubles.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
Dim numbers() As Double = GetRandomNumbers(10)
DisplayRandomNumbers(numbers)
' Persist the numbers as strings.
Dim sw As New StreamWriter("randoms.dat")
For ctr As Integer = 0 To numbers.Length - 1
sw.Write("{0:R}{1}", numbers(ctr), If(ctr < numbers.Length - 1, "|", ""))
Next
sw.Close()
' Read the persisted data.
Dim sr As New StreamReader("randoms.dat")
Dim numericData As String = sr.ReadToEnd()
sr.Close()
Dim numberStrings() As String = numericData.Split("|"c)
' Restore and display the data using the conventions of the en-US culture.
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each numberStr In numberStrings
Dim restoredNumber As Double
If Double.TryParse(numberStr, restoredNumber) Then
Console.WriteLine(restoredNumber.ToString("R"))
Else
Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr)
End If
Next
Console.WriteLine()
' Restore and display the data using the conventions of the fr-FR culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each numberStr In numberStrings
Dim restoredNumber As Double
If Double.TryParse(numberStr, restoredNumber) Then
Console.WriteLine(restoredNumber.ToString("R"))
Else
Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr)
End If
Next
End Sub
Private Function GetRandomNumbers(n As Integer) As Double()
Dim rnd As New Random()
Dim numbers(n - 1) As Double
For ctr As Integer = 0 To n - 1
numbers(ctr) = rnd.NextDouble * 1000
Next
Return numbers
End Function
Private Sub DisplayRandomNumbers(numbers As Double())
For ctr As Integer = 0 To numbers.Length - 1
Console.WriteLine(numbers(ctr).ToString("R"))
Next
Console.WriteLine()
End Sub
End Module
' The example displays output like the following:
' 487.0313743534644
' 674.12000879371533
' 498.72077885024288
' 42.3034229512808
' 970.57311049223563
' 531.33717716268131
' 587.82905693530529
' 562.25210175023039
' 600.7711019370571
' 299.46113717717174
'
' Current Culture: English (United States)
' 487.0313743534644
' 674.12000879371533
' 498.72077885024288
' 42.3034229512808
' 970.57311049223563
' 531.33717716268131
' 587.82905693530529
' 562.25210175023039
' 600.7711019370571
' 299.46113717717174
'
' Current Culture: French (France)
' ERROR: Unable to parse '487.0313743534644'
' ERROR: Unable to parse '674.12000879371533'
' ERROR: Unable to parse '498.72077885024288'
' ERROR: Unable to parse '42.3034229512808'
' ERROR: Unable to parse '970.57311049223563'
' ERROR: Unable to parse '531.33717716268131'
' ERROR: Unable to parse '587.82905693530529'
' ERROR: Unable to parse '562.25210175023039'
' ERROR: Unable to parse '600.7711019370571'
' ERROR: Unable to parse '299.46113717717174'
Um dieses Problem zu vermeiden, können Sie eine der folgenden Techniken verwenden:
- Speichern und analysieren Sie die Zeichenfolgendarstellung der Zahl mithilfe einer benutzerdefinierten Formatzeichenfolge, die unabhängig von der Kultur des Benutzers identisch ist.
- Speichern Sie die Zahl als Zeichenfolge mithilfe der Formatierungskonventionen der invarianten Kultur, die von der CultureInfo.InvariantCulture Eigenschaft zurückgegeben wird.
Das Serialisieren von Währungswerten ist ein Sonderfall. Da ein Währungswert von der Währungseinheit abhängt, 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 das folgende Beispiel zeigt.
using System;
using System.Globalization;
using System.IO;
using System.Threading;
public class Example1
{
public static void Main1()
{
// Display the currency value.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
Decimal value = 16039.47m;
Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
Console.WriteLine($"Currency Value: {value:C2}");
// Persist the currency value as a string.
StreamWriter sw = new StreamWriter("currency.dat");
sw.Write(value.ToString("C2"));
sw.Close();
// Read the persisted data using the current culture.
StreamReader sr = new StreamReader("currency.dat");
string currencyData = sr.ReadToEnd();
sr.Close();
// Restore and display the data using the conventions of the current culture.
Decimal restoredValue;
if (Decimal.TryParse(currencyData, out restoredValue))
Console.WriteLine(restoredValue.ToString("C2"));
else
Console.WriteLine($"ERROR: Unable to parse '{currencyData}'");
Console.WriteLine();
// Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
if (Decimal.TryParse(currencyData, NumberStyles.Currency, null, out restoredValue))
Console.WriteLine(restoredValue.ToString("C2"));
else
Console.WriteLine($"ERROR: Unable to parse '{currencyData}'");
Console.WriteLine();
}
}
// The example displays output like the following:
// Current Culture: English (United States)
// Currency Value: $16,039.47
// ERROR: Unable to parse '$16,039.47'
//
// Current Culture: English (United Kingdom)
// ERROR: Unable to parse '$16,039.47'
Imports System.Globalization
Imports System.IO
Imports System.Threading
Module Example1
Public Sub Main1()
' Display the currency value.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
Dim value As Decimal = 16039.47D
Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
Console.WriteLine("Currency Value: {0:C2}", value)
' Persist the currency value as a string.
Dim sw As New StreamWriter("currency.dat")
sw.Write(value.ToString("C2"))
sw.Close()
' Read the persisted data using the current culture.
Dim sr As New StreamReader("currency.dat")
Dim currencyData As String = sr.ReadToEnd()
sr.Close()
' Restore and display the data using the conventions of the current culture.
Dim restoredValue As Decimal
If Decimal.TryParse(currencyData, restoredValue) Then
Console.WriteLine(restoredValue.ToString("C2"))
Else
Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData)
End If
Console.WriteLine()
' Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
If Decimal.TryParse(currencyData, NumberStyles.Currency, Nothing, restoredValue) Then
Console.WriteLine(restoredValue.ToString("C2"))
Else
Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData)
End If
Console.WriteLine()
End Sub
End Module
' The example displays output like the following:
' Current Culture: English (United States)
' Currency Value: $16,039.47
' ERROR: Unable to parse '$16,039.47'
'
' Current Culture: English (United Kingdom)
' ERROR: Unable to parse '$16,039.47'
Stattdessen sollten Sie den numerischen Wert zusammen mit einigen kulturellen Informationen serialisieren, z. B. den Namen der Kultur, damit der Wert und sein Währungssymbol unabhängig von der aktuellen Kultur deserialisiert werden können. Im folgenden Beispiel wird dies durch Definieren einer CurrencyValue
Struktur mit zwei Elementen ausgeführt: der Decimal Wert und der Name der Kultur, zu der der Wert gehö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 CultureInfo Klasse eine bestimmte Kultur oder Region dar. Einige seiner Eigenschaften geben Objekte zurück, die bestimmte Informationen zu einem bestimmten Aspekt einer Kultur bereitstellen:
Die CultureInfo.CompareInfo Eigenschaft gibt ein CompareInfo Objekt zurück, das Informationen dazu enthält, wie die Kultur Zeichenfolgen vergleicht und sortiert.
Die CultureInfo.DateTimeFormat Eigenschaft gibt ein DateTimeFormatInfo Objekt zurück, das kulturspezifische Informationen bereitstellt, die zum Formatieren von Datums- und Uhrzeitdaten verwendet werden.
Die CultureInfo.NumberFormat Eigenschaft gibt ein NumberFormatInfo Objekt zurück, das kulturspezifische Informationen bereitstellt, die beim Formatieren numerischer Daten verwendet werden.
Die CultureInfo.TextInfo Eigenschaft gibt ein TextInfo Objekt zurück, das Informationen zum Schreibsystem der Kultur bereitstellt.
Machen Sie im Allgemeinen keine Annahmen über die Werte bestimmter CultureInfo Eigenschaften und der zugehörigen Objekte. Stattdessen sollten Sie kulturspezifische Daten aus folgenden Gründen als veränderlich betrachten:
Einzelne Eigenschaftswerte können im Laufe der Zeit geändert und überarbeitet werden, da Daten korrigiert werden, bessere Daten verfügbar werden oder kulturspezifische Konventionen geändert werden.
Einzelne Eigenschaftswerte können je nach Version von .NET oder Betriebssystemversionen variieren.
.NET unterstützt Ersetzungskulturen. Dadurch ist es möglich, eine neue benutzerdefinierte Kultur zu definieren, die bestehende Standardkulturen ergänzt oder vollständig ersetzt.
Auf Windows-Systemen kann der Benutzer kulturspezifische Einstellungen mithilfe der App "Region" und "Sprache " in der Systemsteuerung anpassen. Wenn Sie ein CultureInfo Objekt instanziieren, können Sie ermitteln, ob es diese Benutzeranpassungen widerspiegelt, indem Sie den CultureInfo(String, Boolean) Konstruktor aufrufen. In der Regel sollten Sie für Endbenutzer-Apps Die Benutzereinstellungen berücksichtigen, sodass dem Benutzer Daten in einem erwarteten Format angezeigt werden.