Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Globalizacja obejmuje projektowanie i opracowywanie aplikacji gotowej do użycia na świecie, która obsługuje zlokalizowane interfejsy i dane regionalne dla użytkowników w wielu kulturach. Przed rozpoczęciem fazy projektowania należy określić, które kultury będą obsługiwane przez aplikację. Mimo że aplikacja jest przeznaczona dla pojedynczej kultury lub regionu jako domyślnego, można ją zaprojektować i napisać tak, aby można ją było łatwo rozszerzyć na użytkowników w innych kulturach lub regionach.
Jako deweloperzy mamy założenia dotyczące interfejsów użytkownika i danych tworzonych przez nasze kultury. Na przykład w przypadku anglojęzycznego dewelopera w Stanach Zjednoczonych serializowanie danych daty i godziny jako ciągu w formacie MM/dd/yyyy hh:mm:ss
wydaje się zupełnie rozsądne. Jednak deserializowanie tego ciągu na systemie w innej kulturze prawdopodobnie rzuci wyjątek FormatException lub wygeneruje niedokładne dane. Globalizacja umożliwia nam zidentyfikowanie takich założeń specyficznych dla kultury i upewnienie się, że nie wpływają one na projekt lub kod naszej aplikacji.
W tym artykule omówiono niektóre główne problemy, które należy wziąć pod uwagę, oraz najlepsze rozwiązania, które można zastosować podczas obsługi ciągów, wartości daty i godziny oraz wartości liczbowych w zglobalizowanej aplikacji.
Ciągi
Obsługa znaków i ciągów jest centralnym celem globalizacji, ponieważ każda kultura lub region może używać różnych znaków i zestawów znaków i sortować je inaczej. Ta sekcja zawiera zalecenia dotyczące używania ciągów znaków w aplikacjach zglobalizowanych.
Używanie kodu Unicode wewnętrznie
Domyślnie platforma .NET używa ciągów Unicode. Ciąg Unicode składa się z zera, jednego lub większej liczby Char obiektów, z których każdy reprezentuje jednostkę kodu UTF-16. Istnieje reprezentacja Unicode dla prawie każdego znaku w każdym zestawie znaków używanych na całym świecie.
Wiele aplikacji i systemów operacyjnych, w tym systemu operacyjnego Windows, może również używać stron kodu do reprezentowania zestawów znaków. Strony kodowe zazwyczaj zawierają standardowe wartości ASCII od 0x00 do 0x7F i mapują inne znaki na pozostałe wartości od 0x80 do 0xFF. Interpretacja wartości z 0x80 przez 0xFF zależy od określonej strony kodowej. W związku z tym należy unikać używania stron kodu w zglobalizowanej aplikacji, jeśli jest to możliwe.
Poniższy przykład ilustruje niebezpieczeństwa interpretacji danych strony kodowej, gdy domyślna strona kodowa w systemie różni się od strony kodowej, na której dane zostały zapisane. (Aby zasymulować ten scenariusz, przykład jawnie określa różne strony kodu). Najpierw w przykładzie zdefiniowano tablicę składającą się z wielkich liter alfabetu greckiego. Koduje je w tablicy bajtów przy użyciu strony kodowej 737 (znanej również jako MS-DOS grecki) i zapisuje tablicę bajtów w pliku. Jeśli plik zostanie pobrany, a tablica bajtów zostanie zdekodowana przy użyciu strony kodowej 737, oryginalne znaki zostaną przywrócone. Jeśli jednak plik zostanie pobrany, a tablica bajtów zostanie zdekodowana przy użyciu strony kodowej 1252 (lub Windows-1252, która reprezentuje znaki alfabetu łacińskiego), oryginalne znaki zostaną utracone.
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:
' ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
' €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—
Użycie formatu Unicode gwarantuje, że te same jednostki kodu zawsze mapowane są na te same znaki i że te same znaki są zawsze mapowane na te same tablice bajtów.
Korzystanie z plików zasobów
Nawet jeśli tworzysz aplikację przeznaczoną dla pojedynczej kultury lub regionu, należy użyć plików zasobów do przechowywania ciągów i innych zasobów wyświetlanych w interfejsie użytkownika. Nigdy nie należy dodawać ich bezpośrednio do kodu. Korzystanie z plików zasobów ma wiele zalet:
- Wszystkie ciągi znajdują się w jednej lokalizacji. Nie musisz wyszukiwać w całym kodzie źródłowym, aby identyfikować ciągi, które mają być modyfikowane dla określonego języka lub kultury.
- Nie ma potrzeby duplikowania ciągów. Deweloperzy, którzy nie używają plików zasobów, często definiują ten sam ciąg w wielu plikach kodu źródłowego. Duplikowanie zwiększa prawdopodobieństwo, że co najmniej jedno wystąpienie zostanie pominięte po zmodyfikowaniu ciągu.
- Zasoby inne niż ciągi, takie jak obrazy lub dane binarne, można uwzględnić w pliku zasobów zamiast przechowywać je w osobnym pliku autonomicznym, aby można je było łatwo pobrać.
Korzystanie z plików zasobów ma szczególne zalety w przypadku tworzenia zlokalizowanej aplikacji. Podczas wdrażania zasobów w zestawach satelickich środowisko uruchomieniowe wspólnego języka (CLR) automatycznie wybiera zasób odpowiedni dla bieżącej kultury interfejsu użytkownika, zdefiniowanej przez właściwość CultureInfo.CurrentUICulture. Jeśli podasz odpowiedni zasób specyficzny dla kultury i poprawnie utworzysz wystąpienie ResourceManager obiektu lub użyjesz silnie typizowanej klasy zasobów, środowisko uruchomieniowe przejmuje odpowiedzialność za pobieranie odpowiednich zasobów.
Aby uzyskać więcej informacji na temat tworzenia plików zasobów, zobacz Tworzenie plików zasobów. Aby uzyskać informacje o tworzeniu i wdrażaniu zestawów satelickich, zobacz Tworzenie zestawów satelickich oraz Tworzenie pakietów i wdrażanie zasobów.
Wyszukiwanie i porównywanie ciągów
Jeśli to możliwe, należy obsługiwać ciągi jako całe ciągi zamiast obsługiwać je jako serię pojedynczych znaków. Jest to szczególnie ważne w przypadku sortowania lub wyszukiwania podciągów, aby zapobiec problemom związanym z analizowaniem połączonych znaków.
Wskazówka
Możesz użyć StringInfo klasy do pracy z elementami tekstowymi, a nie z poszczególnymi znakami w ciągu.
W wyszukiwaniu ciągów i porównaniach typowym błędem jest traktowanie ciągu jako kolekcji znaków, z których każdy jest reprezentowany przez Char obiekt. W rzeczywistości pojedynczy znak może być tworzony przez jeden, dwa lub więcej Char obiektów. Takie znaki są najczęściej spotykane w ciągach z kultur, których alfabety składają się z znaków poza zakresem znaków Unicode Basic Latin (od U+0021 do U+007E). Poniższy przykład próbuje znaleźć indeks alfabetu łacińskiego WIELKA LITERA A Z znakiem GRAVE (U+00C0) w ciągu. Ten znak można jednak przedstawić na dwa różne sposoby: jako pojedynczą jednostkę kodu (U+00C0) lub jako znak złożony (dwie jednostki kodu: U+0041 i U+0300). W tym przypadku znak jest reprezentowany w wystąpieniu ciągu znaków przez dwa obiekty Char, U+0041 i U+0300. Przykładowy kod wywołuje przeciążenia String.IndexOf(Char) i String.IndexOf(String) w celu znaleźć pozycję tego znaku w wystąpieniu ciągu, ale zwracają one różne wyniki. Pierwsze wywołanie metody ma Char argument; wykonuje porównanie porządkowe i dlatego nie może odnaleźć dopasowania. Drugie wywołanie ma String argument; przeprowadza porównanie uwzględniające dane kulturowe i dlatego znajduje dopasowanie.
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
Można uniknąć niektórych niejednoznaczności tego przykładu (wywołania dwóch podobnych przeciążeń metody, które zwracają różne wyniki), wybierając przeciążenie zawierające parametr StringComparison, takie jak metoda String.IndexOf(String, StringComparison) lub String.LastIndexOf(String, StringComparison).
Jednak wyszukiwanie nie zawsze jest wrażliwe na kulturę. Jeśli celem wyszukiwania jest podjęcie decyzji dotyczącej zabezpieczeń lub zezwolenie lub nie zezwalanie na dostęp do niektórych zasobów, porównanie powinno być porządkowe zgodnie z opisem w następnej sekcji.
Testowanie ciągów pod kątem równości
Jeśli chcesz przetestować dwa ciągi pod kątem równości, a nie określić sposobu ich porównywania w kolejności sortowania, użyj String.Equals metody zamiast metody porównania ciągów, takiej jak String.Compare lub CompareInfo.Compare.
Porównania równości są zwykle wykonywane w celu warunkowego uzyskania dostępu do niektórych zasobów. Na przykład można wykonać porównanie równości w celu zweryfikowania hasła lub potwierdzenia, że plik istnieje. Takie porównania nielingwistyczne powinny być zawsze porządkowe, a nie wrażliwe na kulturę. Ogólnie rzecz biorąc, należy wywołać metodę wystąpienia String.Equals(String, StringComparison) lub metodę statyczną String.Equals(String, String, StringComparison) z wartością StringComparison.Ordinal dla ciągów, takich jak hasła, oraz wartość StringComparison.OrdinalIgnoreCase dla ciągów, takich jak nazwy plików lub identyfikatory URI.
Porównania równości czasami obejmują wyszukiwania lub porównania podciągów, zamiast wywołań metody String.Equals. W niektórych przypadkach można użyć wyszukiwania podciągów, aby określić, czy podciąg jest równy innemu ciągowi. Jeśli celem tego porównania jest nielingwistyczna, wyszukiwanie powinno być również porządkowe, a nie wrażliwe na kulturę.
Poniższy przykład ilustruje niebezpieczeństwo wyszukiwania wrażliwego na kulturę danych nielingwistycznych. Metoda AccessesFileSystem
została zaprojektowana, aby uniemożliwić identyfikatorom URI, które rozpoczynają się od podciągu "FILE", dostęp do systemu plików. W tym celu wykonuje porównanie z uwzględnieniem ustawień regionalnych, ignorując wielkość liter, na początku identyfikatora URI za pomocą ciągu "FILE". Ponieważ identyfikator URI, który uzyskuje dostęp do systemu plików, zaczyna się od "FILE:" lub "file:", niejawnym założeniem jest to, że "i" (U+0069) jest zawsze odpowiednikiem małej litery "I" (U+0049). Jednak w języku tureckim i azerbejdżańskim wersja wielkiej litery "i" to "İ" (U+0130). Ze względu na tę rozbieżność, porównanie uwzględniające specyfikę kulturową umożliwia dostęp do systemu plików, mimo że powinien on być zabroniony.
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.
Ten problem można uniknąć, wykonując porównanie porządkowe, które ignoruje wielkość liter, jak pokazano w poniższym przykładzie.
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.
Kolejność i sortowanie ciągów
Zazwyczaj uporządkowane ciągi, które mają być wyświetlane w interfejsie użytkownika, powinny być sortowane na podstawie kultury. W większości przypadków takie porównania ciągów są obsługiwane niejawnie przez platformę .NET podczas wywoływania metody, która sortuje ciągi, takie jak Array.Sort lub List<T>.Sort. Domyślnie ciągi są sortowane przy użyciu konwencji sortowania bieżącej kultury. Poniższy przykład ilustruje różnicę, gdy tablica ciągów jest sortowana przy użyciu konwencji kultury angielskiej (Stanów Zjednoczonych) i kultury szwedzkiej (Szwecja).
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
Porównanie ciągów wrażliwych na kulturę jest definiowane przez obiekt CompareInfo, który jest zwracany przez właściwość odpowiedniej kultury CultureInfo.CompareInfo. Porównania ciągów znaków uwzględniających kulturę, które korzystają z przeciążeń metody String.Compare, również używają obiektu CompareInfo.
Platforma .NET używa tabel do przeprowadzania sortowania uwzględniającego lokalne ustawienia kulturowe na danych tekstowych. Zawartość tych tabel, które zawierają dane dotyczące wag sortowania i normalizacji ciągów, jest określana przez wersję standardu Unicode zaimplementowaną przez określoną wersję platformy .NET. W poniższej tabeli wymieniono wersje Unicode zaimplementowane przez określone wersje platformy .NET. Ta lista obsługiwanych wersji Unicode dotyczy tylko porównania znaków i sortowania; nie ma zastosowania do klasyfikacji znaków Unicode według kategorii. Aby uzyskać więcej informacji, zapoznaj się z sekcją "Ciągi i Standard Unicode" w artykule String.
Wersja programu .NET Framework | System operacyjny | Wersja Unicode |
---|---|---|
.NET Framework 2.0 | Wszystkie systemy operacyjne | Unicode 4.1 |
.NET Framework 3.0 | Wszystkie systemy operacyjne | Unicode 4.1 |
.NET Framework 3.5 | Wszystkie systemy operacyjne | Unicode 4.1 |
.NET Framework 4 | Wszystkie systemy operacyjne | Unicode 5.0 |
Program .NET Framework 4.5 lub nowszy | Windows 7 | Unicode 5.0 |
Program .NET Framework 4.5 lub nowszy | Systemy operacyjne Windows 8 i nowsze | Unicode 6.3.0 |
.NET Core i .NET 5+ | Zależy od wersji standardu Unicode obsługiwanej przez bazowy system operacyjny. |
Począwszy od programu .NET Framework 4.5 i we wszystkich wersjach platformy .NET Core i .NET 5+, porównanie ciągów i sortowanie zależy od systemu operacyjnego. Program .NET Framework 4.5 lub nowszy w systemie Windows 7 pobiera dane z własnych tabel, które implementują standard Unicode 5.0. Program .NET Framework 4.5 lub nowszy z systemem Windows 8 lub nowszym pobiera dane z tabel systemu operacyjnego, które implementują standard Unicode 6.3. W programach .NET Core i .NET 5 lub nowszych obsługiwana wersja Unicode zależy od bazowego systemu operacyjnego. W przypadku serializacji danych posortowanych z uwzględnieniem kultury można użyć SortVersion klasy , aby określić, kiedy dane serializowane muszą być sortowane, tak aby były zgodne z platformą .NET i kolejnością sortowania systemu operacyjnego. Aby zapoznać się z przykładem, zobacz temat dotyczący klasy SortVersion.
Jeśli Twoja aplikacja wykonuje złożone sortowania ciągów znaków dla specyficznych kultur, możesz pracować z klasą SortKey, aby porównać ciągi. Klucz sortowania odzwierciedla specyficzne dla kultury wagi sortowania, w tym wagi alfabetyczne, wagi wielkości liter oraz wagi diakrytyczne określonego ciągu. Ponieważ porównania używające kluczy sortowania są binarne, są szybsze niż porównania, które używają CompareInfo obiektu jawnie lub niejawnie. Należy utworzyć klucz sortowania specyficzny dla kultury dla określonego ciągu, przekazując ciąg do CompareInfo.GetSortKey metody .
Poniższy przykład jest podobny do poprzedniego przykładu. Jednak zamiast wywoływać metodę Array.Sort(Array), która niejawnie wywołuje metodę CompareInfo.Compare, definiuje implementację System.Collections.Generic.IComparer<T>, która porównuje klucze sortowania, tworząc jego wystąpienie i przekazuje ją do metody Array.Sort<T>(T[], IComparer<T>).
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
public class SortKeyComparer : IComparer<String>
{
public int Compare(string? str1, string? str2)
{
return (str1, str2) switch
{
(null, null) => 0,
(null, _) => -1,
(_, null) => 1,
(var s1, var s2) => SortKey.Compare(
CultureInfo.CurrentCulture.CompareInfo.GetSortKey(s1),
CultureInfo.CurrentCulture.CompareInfo.GetSortKey(s1))
};
}
}
public class Example19
{
public static void Main19()
{
string[] values = { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
SortKeyComparer comparer = new SortKeyComparer();
// Change thread to en-US.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
// Sort the array and copy it to a new array to preserve the order.
Array.Sort(values, comparer);
string[] enValues = (String[])values.Clone();
// Change culture to Swedish (Sweden).
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("sv-SE");
Array.Sort(values, comparer);
string[] svValues = (String[])values.Clone();
// Compare the sorted arrays.
Console.WriteLine("{0,-8} {1,-15} {2,-15}\n", "Position", "en-US", "sv-SE");
for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues[ctr], svValues[ctr]);
}
}
// The example displays the following output:
// Position en-US sv-SE
//
// 0 able able
// 1 Æble Æble
// 2 ångström apple
// 3 apple Windows
// 4 Visual Studio Visual Studio
// 5 Windows ångström
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Threading
Public Class SortKeyComparer : Implements IComparer(Of String)
Public Function Compare(str1 As String, str2 As String) As Integer _
Implements IComparer(Of String).Compare
Dim sk1, sk2 As SortKey
sk1 = CultureInfo.CurrentCulture.CompareInfo.GetSortKey(str1)
sk2 = CultureInfo.CurrentCulture.CompareInfo.GetSortKey(str2)
Return SortKey.Compare(sk1, sk2)
End Function
End Class
Module Example19
Public Sub Main19()
Dim values() As String = {"able", "ångström", "apple",
"Æble", "Windows", "Visual Studio"}
Dim comparer As New SortKeyComparer()
' Change thread to en-US.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
' Sort the array and copy it to a new array to preserve the order.
Array.Sort(values, comparer)
Dim enValues() As String = CType(values.Clone(), String())
' Change culture to Swedish (Sweden).
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values, comparer)
Dim svValues() As String = CType(values.Clone(), String())
' Compare the sorted arrays.
Console.WriteLine("{0,-8} {1,-15} {2,-15}", "Position", "en-US", "sv-SE")
Console.WriteLine()
For ctr As Integer = 0 To values.GetUpperBound(0)
Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues(ctr), svValues(ctr))
Next
End Sub
End Module
' The example displays the following output:
' Position en-US sv-SE
'
' 0 able able
' 1 Æble Æble
' 2 ångström apple
' 3 apple Windows
' 4 Visual Studio Visual Studio
' 5 Windows ångström
Unikaj łączenia ciągów
Jeśli w ogóle jest to możliwe, unikaj używania złożonych ciągów utworzonych w czasie wykonywania z połączonych fraz. Ciągi złożone są trudne do zlokalizowania, ponieważ często zakładają one kolejność gramatyczną w oryginalnym języku aplikacji, który nie ma zastosowania do innych zlokalizowanych języków.
Obsługa dat i godzin
Sposób obsługi wartości daty i godziny zależy od tego, czy są one wyświetlane w interfejsie użytkownika, czy utrwalone. W tej sekcji przeanalizować oba zastosowania. Omówiono również sposób obsługi różnic strefy czasowej i operacji arytmetycznych podczas pracy z datami i godzinami.
Wyświetlanie dat i godzin
Zazwyczaj, kiedy daty i godziny są wyświetlane w interfejsie użytkownika, należy użyć konwencji formatowania kulturowego użytkownika, które są definiowane przez właściwość CultureInfo.CurrentCulture i obiekt DateTimeFormatInfo zwrócony przez właściwość CultureInfo.CurrentCulture.DateTimeFormat
. Konwencje formatowania bieżącej kultury są automatycznie używane podczas formatowania daty przy użyciu dowolnej z następujących metod:
- Metoda bez DateTime.ToString() parametrów.
- Metoda DateTime.ToString(String) , która zawiera ciąg formatu.
- Metoda bez DateTimeOffset.ToString() parametrów.
- Element DateTimeOffset.ToString(String), który zawiera ciąg formatu.
- Funkcja formatowania złożonego , gdy jest używana z datami.
W poniższym przykładzie są wyświetlane dane o wschodzie słońca i zachodzie słońca dwa razy dla 11 października 2012 r. Najpierw ustawia obecną kulturę na Chorwacki (Chorwacja), a następnie na angielski (Wielka Brytania). W każdym przypadku daty i godziny są wyświetlane w formacie odpowiednim dla tej kultury.
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
Utrwalanie dat i godzin
Nigdy nie należy utrwalać danych daty i godziny w formacie, który może się różnić w zależności od kultury. Jest to typowy błąd programowania, który powoduje uszkodzenie danych lub błąd w trakcie wykonywania. Poniższy przykład serializuje dwie daty, 9 stycznia 2013 r. i 18 sierpnia 2013 r., jako ciągi przy użyciu konwencji formatowania kultury angielskiej (Stany Zjednoczone). Po pobraniu i przeanalizowaniu danych przy użyciu konwencji kultury angielskiej (Stanów Zjednoczonych) zostanie ona pomyślnie przywrócona. Jednak po pobraniu i przeanalizowaniu z użyciem konwencji kulturowych angielskich (Zjednoczone Królestwo) pierwsza data jest błędnie interpretowana jako 1 września, a druga nie można przeanalizować, ponieważ kalendarz gregoriański nie ma osiemnastego miesiąca.
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
Ten problem można uniknąć na dowolny z trzech sposobów:
- Serializuj datę i godzinę w formacie binarnym, a nie jako ciąg.
- Zapisz i przeanalizuj ciąg reprezentujący datę i godzinę przy użyciu niestandardowego ciągu formatu, który jest taki sam, niezależnie od kultury użytkownika.
- Zapisz ciąg przy użyciu konwencji formatowania niezmiennej kultury.
Poniższy przykład ilustruje ostatnie podejście. Używa konwencji formatowania niezmiennej kultury zwracanej przez właściwość statyczną CultureInfo.InvariantCulture .
using System;
using System.IO;
using System.Globalization;
using System.Threading;
public class Example5
{
public static void Main5()
{
// Persist two dates as strings.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
DateTime[] dates = { new DateTime(2013, 1, 9),
new DateTime(2013, 8, 18) };
StreamWriter sw = new StreamWriter("dateData.dat");
sw.Write(String.Format(CultureInfo.InvariantCulture,
"{0:d}|{1:d}", dates[0], dates[1]));
sw.Close();
// Read the persisted data.
StreamReader sr = new StreamReader("dateData.dat");
string dateData = sr.ReadToEnd();
sr.Close();
string[] dateStrings = dateData.Split('|');
// Restore and display the data using the conventions of the en-US culture.
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var dateStr in dateStrings)
{
DateTime restoredDate;
if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, out restoredDate))
Console.WriteLine($"The date is {restoredDate:D}");
else
Console.WriteLine($"ERROR: Unable to parse {dateStr}");
}
Console.WriteLine();
// Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
foreach (var dateStr in dateStrings)
{
DateTime restoredDate;
if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, out restoredDate))
Console.WriteLine($"The date is {restoredDate:D}");
else
Console.WriteLine($"ERROR: Unable to parse {dateStr}");
}
}
}
// The example displays the following output:
// Current Culture: English (United States)
// The date is Wednesday, January 09, 2013
// The date is Sunday, August 18, 2013
//
// Current Culture: English (United Kingdom)
// The date is 09 January 2013
// The date is 18 August 2013
Imports System.Globalization
Imports System.IO
Imports System.Threading
Module Example5
Public Sub Main5()
' Persist two dates as strings.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
Dim dates() As DateTime = {New DateTime(2013, 1, 9),
New DateTime(2013, 8, 18)}
Dim sw As New StreamWriter("dateData.dat")
sw.Write(String.Format(CultureInfo.InvariantCulture,
"{0:d}|{1:d}", dates(0), dates(1)))
sw.Close()
' Read the persisted data.
Dim sr As New StreamReader("dateData.dat")
Dim dateData As String = sr.ReadToEnd()
sr.Close()
Dim dateStrings() As String = dateData.Split("|"c)
' Restore and display the data using the conventions of the en-US culture.
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each dateStr In dateStrings
Dim restoredDate As Date
If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, restoredDate) Then
Console.WriteLine("The date is {0:D}", restoredDate)
Else
Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
End If
Next
Console.WriteLine()
' Restore and display the data using the conventions of the en-GB culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.DisplayName)
For Each dateStr In dateStrings
Dim restoredDate As Date
If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
DateTimeStyles.None, restoredDate) Then
Console.WriteLine("The date is {0:D}", restoredDate)
Else
Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
End If
Next
End Sub
End Module
' The example displays the following output:
' Current Culture: English (United States)
' The date is Wednesday, January 09, 2013
' The date is Sunday, August 18, 2013
'
' Current Culture: English (United Kingdom)
' The date is 09 January 2013
' The date is 18 August 2013
Serializacja i rozpoznawanie stref czasowych
Wartość daty i godziny może mieć wiele interpretacji, od ogólnego czasu ("Sklepy otwarte 2 stycznia 2013 r., o 9:00 rano") do określonej chwili w czasie ("Data urodzenia: 2 stycznia 2013 6:32:00 czasu"). Gdy wartość godziny reprezentuje określony moment w czasie i przywracasz ją z serializowanej wartości, upewnij się, że reprezentuje ten sam moment w czasie niezależnie od lokalizacji geograficznej lub strefy czasowej użytkownika.
Poniższy przykład ilustruje ten problem. Zapisuje pojedynczą lokalną wartość daty i godziny jako ciąg w trzech standardowych formatach:
- "G" dla ogólnej daty i długiego czasu.
- "s" dla sortowalnej daty/godziny.
- "o" dla daty/godziny w obie strony.
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
Gdy dane są przywracane w systemie w tej samej strefie czasowej co system, w którym została serializowana, deserializowane wartości daty i godziny dokładnie odzwierciedlają oryginalną wartość, jak pokazują dane wyjściowe:
'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
Jeśli jednak przywrócisz dane w systemie w innej strefie czasowej, tylko wartość daty i godziny sformatowana za pomocą standardowego ciągu formatu "o" (round-trip) zachowuje informacje o strefie czasowej i w związku z tym reprezentuje tę samą chwilę w czasie. Oto dane wyjściowe, kiedy dane daty i godziny są przywracane w systemie w strefie czasowej Romans (standardowy):
'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
Aby dokładnie odzwierciedlić wartość daty i godziny reprezentującą pojedynczą chwilę czasu niezależnie od strefy czasowej systemu, na którym są deserializowane dane, można wykonać dowolną z następujących czynności:
- Zapisz wartość jako ciąg przy użyciu standardowego ciągu formatu "o" (round-trip). Następnie należy deserializować go w systemie docelowym.
- Przekonwertuj go na czas UTC i zapisz go jako ciąg przy użyciu standardowego ciągu formatu "r" (RFC1123). Następnie deserializuj go w systemie docelowym i przekonwertuj go na czas lokalny.
- Przekonwertuj go na czas UTC i zapisz go jako ciąg przy użyciu standardowego ciągu formatu "u" (uniwersalnego sortowania). Następnie deserializuj go w systemie docelowym i przekonwertuj go na czas lokalny.
Poniższy przykład ilustruje każdą technikę.
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
Gdy dane są serializowane w systemie w strefie czasowej czasu pacyficznego i deserializowane w systemie w strefie czasowej czasu romańskiego, w przykładzie są wyświetlane następujące wyniki wyjściowe:
'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
Aby uzyskać więcej informacji, zobacz Konwertowanie czasów między strefami czasowymi.
Wykonywanie arytmetyki daty i godziny
Oba typy DateTime i DateTimeOffset obsługują operacje arytmetyczne. Możesz obliczyć różnicę między dwiema wartościami daty lub dodać lub odjąć określone interwały czasu do lub z wartości daty. Jednak operacje arytmetyczne w wartościach daty i godziny nie uwzględniają stref czasowych i reguł korekty strefy czasowej. W związku z tym arytmetyka daty i godziny dla wartości reprezentujących momenty w czasie może zwracać niedokładne wyniki.
Na przykład, przejście z czasu standardowego Pacyfiku na czas letni Pacyfiku odbywa się w drugą niedzielę marca, czyli 10 marca 2013 r. Jak pokazano w poniższym przykładzie, jeśli obliczysz datę i godzinę 48 godzin po 9 marca 2013 r. o godzinie 10:30 w systemie w strefie czasowej czasu standardowego pacyficznego, wynik, 11 marca 2013 r. o godzinie 10:30, nie uwzględnia zmiany czasu.
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
Aby upewnić się, że operacja arytmetyczna w wartościach daty i godziny daje dokładne wyniki, wykonaj następujące kroki:
- Przekonwertuj czas w źródłowej strefie czasowej na utc.
- Wykonaj operację arytmetyczną.
- Jeśli wynik jest wartością daty i godziny, przekonwertuj ją z utc na godzinę w źródłowej strefie czasowej.
Poniższy przykład jest podobny do poprzedniego przykładu, z tą różnicą, że wykonuje te trzy kroki, aby poprawnie dodać 48 godzin do 9 marca 2013 r. o godzinie 10:30.
using System;
public class Example8
{
public static void Main8()
{
TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
DateTime date1 = DateTime.SpecifyKind(new DateTime(2013, 3, 9, 10, 30, 0),
DateTimeKind.Local);
DateTime utc1 = date1.ToUniversalTime();
TimeSpan interval = new TimeSpan(48, 0, 0);
DateTime utc2 = utc1 + interval;
DateTime date2 = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst);
Console.WriteLine($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
}
}
// The example displays the following output:
// 3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM
Module Example8
Public Sub Main8()
Dim pst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")
Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
DateTimeKind.Local)
Dim utc1 As Date = date1.ToUniversalTime()
Dim interval As New TimeSpan(48, 0, 0)
Dim utc2 As Date = utc1 + interval
Dim date2 As Date = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst)
Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
date1, interval.TotalHours, date2)
End Sub
End Module
' The example displays the following output:
' 3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM
Aby uzyskać więcej informacji, zobacz Wykonywanie operacji arytmetycznych z datami i godzinami.
Używaj kulturowo adekwatnych nazw dla elementów daty
Aplikacja może wymagać wyświetlenia nazwy miesiąca lub dnia tygodnia. W tym celu kod, taki jak poniżej, jest typowy.
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.
Jednak ten kod zawsze zwraca nazwy dni tygodnia w języku angielskim. Kod, który wyodrębnia nazwę miesiąca, jest często jeszcze bardziej nieelastyczny. Często przyjmuje się dwunastomiesięczny kalendarz z nazwami miesięcy w określonym języku.
Używając niestandardowych ciągów formatu daty i godziny lub właściwości DateTimeFormatInfo obiektu, można łatwo wyodrębnić ciągi odzwierciedlające nazwy dni tygodnia lub miesięcy w kulturze użytkownika, jak pokazano w poniższym przykładzie. Zmienia bieżącą kulturę na francuski (Francja) i wyświetla nazwę dnia tygodnia oraz nazwę miesiąca dla 1 lipca 2013 r.
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.
Wartości liczbowe
Obsługa liczb zależy od tego, czy są wyświetlane w interfejsie użytkownika, czy utrwalone. W tej sekcji przeanalizować oba zastosowania.
Uwaga / Notatka
W operacjach analizowania i formatowania platforma .NET rozpoznaje tylko podstawowe znaki łacińskie od 0 do 9 (od U+0030 do U+0039) jako cyfry liczbowe.
Wyświetlanie wartości liczbowych
Zazwyczaj, gdy liczby są wyświetlane w interfejsie użytkownika, należy użyć konwencji formatowania kultury użytkownika, którą definiują właściwości CultureInfo.CurrentCulture i obiekt NumberFormatInfo zwrócony przez właściwości CultureInfo.CurrentCulture.NumberFormat
. Konwencje formatowania bieżącej kultury są automatycznie używane podczas formatowania daty na następujące sposoby:
- Użycie metody
ToString
bez parametrów dla dowolnego typu liczbowego. - Używając metody
ToString(String)
dowolnego typu liczbowego, która zawiera ciąg formatu jako argument. - Używanie formatowania złożonego z wartościami liczbowymi.
W poniższym przykładzie przedstawiono średnią temperaturę miesięcznie w Paryżu we Francji. Najpierw ustawia bieżącą kulturę na francuską (Francja) przed wyświetleniem danych, a następnie ustawia ją na angielski (Stany Zjednoczone). W każdym przypadku nazwy i temperatury miesiąca są wyświetlane w formacie odpowiednim dla tej kultury. Należy pamiętać, że dwie kultury używają różnych separatorów dziesiętnych w wartości temperatury. Należy również pamiętać, że w przykładzie użyto niestandardowego ciągu formatu daty i godziny "MMMM", aby wyświetlić pełną nazwę miesiąca i że przydziela odpowiednią ilość miejsca dla nazwy miesiąca w ciągu wynikowym, określając długość najdłuższej nazwy miesiąca w tablicy DateTimeFormatInfo.MonthNames .
using System;
using System.Globalization;
using System.Threading;
public class Example14
{
public static void Main14()
{
DateTime dateForMonth = new DateTime(2013, 1, 1);
double[] temperatures = { 3.4, 3.5, 7.6, 10.4, 14.5, 17.2,
19.9, 18.2, 15.9, 11.3, 6.9, 5.3 };
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");
Console.WriteLine($"Current Culture: {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
Utrwalanie wartości liczbowych
Nigdy nie należy utrwalać danych liczbowych w formacie specyficznym dla kultury. Jest to typowy błąd programowania, który powoduje uszkodzenie danych lub błąd w trakcie wykonywania. Poniższy przykład generuje dziesięć losowych liczb zmiennoprzecinkowych, a następnie serializuje je jako ciągi przy użyciu konwencji formatowania kultury języka angielskiego (Stanów Zjednoczonych). Po pobraniu i przeanalizowaniu danych przy użyciu konwencji kultury angielskiej (Stanów Zjednoczonych) zostanie ona pomyślnie przywrócona. Jednak po pobraniu i przeanalizowaniu przy użyciu konwencji kultury francuskiej (Francji) żadna z liczb nie może być analizowana, ponieważ kultury używają różnych separatorów dziesiętnych.
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'
Aby uniknąć tego problemu, możesz użyć jednej z następujących technik:
- Zapisz i przeanalizuj ciąg reprezentujący liczbę przy użyciu niestandardowego ciągu formatu, który jest taki sam, niezależnie od kultury użytkownika.
- Zapisz liczbę jako ciąg znaków, używając konwencji formatowania niezmiennej kultury zwracanej przez właściwość CultureInfo.InvariantCulture.
Serializowanie wartości walutowych jest specjalnym przypadkiem. Ponieważ wartość waluty zależy od jednostki waluty, w której jest wyrażona, nie ma sensu traktować jej jako niezależnej wartości liczbowej. Jeśli jednak zapiszesz wartość waluty jako sformatowany ciąg, który zawiera symbol waluty, nie może być deserializowany w systemie, którego kultura domyślna używa innego symbolu waluty, jak pokazano w poniższym przykładzie.
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'
Zamiast tego należy serializować wartość liczbową wraz z niektórymi informacjami kulturowymi, takimi jak nazwa kultury, aby wartość i jej symbol waluty mogły być deserializowane niezależnie od bieżącej kultury. W poniższym przykładzie definiuje się strukturę CurrencyValue
z dwoma członkami: wartością Decimal i nazwą kultury, której wartość należy.
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
Praca z ustawieniami charakterystycznymi dla danej kultury
Na platformie .NET CultureInfo klasa reprezentuje określoną kulturę lub region. Niektóre jego właściwości zwracają obiekty, które zawierają określone informacje o pewnym aspekcie kultury:
Właściwość CultureInfo.CompareInfo zwraca CompareInfo obiekt zawierający informacje o tym, jak kultura porównuje i sortuje ciągi.
Właściwość CultureInfo.DateTimeFormat przekazuje obiekt DateTimeFormatInfo, który udostępnia informacje specyficzne dla kultury, używane w formatowaniu danych dotyczących daty i godziny.
Właściwość CultureInfo.NumberFormat zwraca NumberFormatInfo obiekt, który udostępnia informacje specyficzne dla kultury używane w formatowaniu danych liczbowych.
Właściwość CultureInfo.TextInfo zwraca TextInfo obiekt, który zawiera informacje o systemie pisania kultury.
Ogólnie rzecz biorąc, nie należy zakładać wartości określonych CultureInfo właściwości i powiązanych obiektów. Zamiast tego należy wyświetlać dane specyficzne dla kultury jako podlegające zmianie z następujących powodów:
Poszczególne wartości właściwości podlegają zmianom i poprawce w czasie, ponieważ dane są poprawiane, lepsze dane stają się dostępne lub zmieniają się konwencje specyficzne dla kultury.
Poszczególne wartości właściwości mogą się różnić w różnych wersjach platformy .NET lub systemu operacyjnego.
Platforma .NET obsługuje kultury zastępcze. Dzięki temu można zdefiniować nową kulturę niestandardową, która uzupełnia istniejące kultury standardowe lub całkowicie zastępuje istniejącą kulturę standardową.
W systemach Windows użytkownik może dostosować ustawienia specyficzne dla kultury przy użyciu aplikacji Region i język w Panelu sterowania. Podczas tworzenia wystąpienia obiektu CultureInfo można określić, czy odzwierciedla on dostosowania użytkownika, wywołując konstruktor CultureInfo(String, Boolean). Zazwyczaj w przypadku aplikacji użytkowników końcowych należy przestrzegać preferencji użytkownika, aby użytkownik był prezentowany dane w oczekiwanym formacie.