Globalizacja platformy .NET i ICU
Przed platformą .NET 5 interfejsy API globalizacji platformy .NET używały różnych bibliotek bazowych na różnych platformach. W systemie Unix interfejsy API używały międzynarodowych składników dla standardu Unicode (ICU) i w systemie Windows używały obsługi języka narodowego (NLS). Spowodowało to pewne różnice behawioralne w kilku interfejsach API globalizacji podczas uruchamiania aplikacji na różnych platformach. Różnice zachowań były widoczne w następujących obszarach:
- Kultury i dane kulturowe
- Wielkość liter ciągów
- Sortowanie i wyszukiwanie ciągów
- Sortowanie kluczy
- Normalizacja ciągu
- Obsługa nazw domen międzynarodowych (IDN)
- Nazwa wyświetlana strefy czasowej w systemie Linux
Począwszy od platformy .NET 5, deweloperzy mają większą kontrolę nad tym, która podstawowa biblioteka jest używana, umożliwiając aplikacjom unikanie różnic między platformami.
Uwaga
Dane kulturowe, które napędzają zachowanie biblioteki ICU, są zwykle obsługiwane przez repozytorium Common Locale Data Repository (CLDR), a nie środowisko uruchomieniowe.
ICU w systemie Windows
System Windows zawiera teraz wstępnie zainstalowaną wersję icu.dll w ramach funkcji, które są automatycznie stosowane do zadań globalizacji. Ta modyfikacja umożliwia platformie .NET wykorzystanie tej biblioteki ICU do obsługi globalizacji. W przypadkach, gdy biblioteka ICU jest niedostępna lub nie można jej załadować, podobnie jak w przypadku starszych wersji systemu Windows, platformy .NET 5 i kolejnych wersji powrócić do korzystania z implementacji opartej na nlS.
W poniższej tabeli przedstawiono, które wersje platformy .NET mogą ładować bibliotekę ICU w różnych wersjach klienta i serwera systemu Windows:
Wersja platformy .NET | Wersja dla systemu Windows |
---|---|
.NET 5 lub .NET 6 | Klient systemu Windows 10 w wersji 1903 lub nowszej |
.NET 5 lub .NET 6 | Windows Server 2022 lub nowszy |
.NET 7 lub nowszy | Klient systemu Windows 10 w wersji 1703 lub nowszej |
.NET 7 lub nowszy | Windows Server 2019 lub nowszy |
Uwaga
Platforma .NET 7 i nowsze wersje mają możliwość ładowania ICU w starszych wersjach systemu Windows, w przeciwieństwie do platformy .NET 6 i .NET 5.
Uwaga
Nawet w przypadku korzystania z ICU, CurrentCulture
CurrentUICulture
, i CurrentRegion
członków nadal używają interfejsów API systemu operacyjnego Windows do przestrzegania ustawień użytkownika.
Różnice behawioralne
Jeśli uaktualnisz aplikację do platformy .NET 5 lub nowszej, możesz zobaczyć zmiany w aplikacji, nawet jeśli nie zdajesz sobie sprawy, że używasz obiektów globalizacji. W tej sekcji wymieniono jedną ze zmian behawioralnych, które mogą zostać wyświetlone, ale są też inne.
String.IndexOf
Rozważ następujący kod, który wywołuje String.IndexOf(String) metodę w celu znalezienia indeksu znaku \0
o wartości null w ciągu.
const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.CurrentCulture)}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.Ordinal)}");
- W programie .NET Core 3.1 i starszych wersjach systemu Windows fragment kodu jest drukowany w każdym z trzech wierszy
3
. - W przypadku platformy .NET 5 i nowszych wersji działających w wersjach systemu Windows wymienionych w tabeli sekcji ICU w systemie Windows fragment kodu drukuje
0
wartości ,0
i3
(dla wyszukiwania porządkowego).
Domyślnie String.IndexOf(String) wykonuje wyszukiwanie językowe z uwzględnieniem kultury. Funkcja ICU uważa, że znak \0
null jest znakiem zerowej wagi, a zatem znak nie znajduje się w ciągu podczas wyszukiwania językowego na platformie .NET 5 i nowszych. Jednak nlS nie uwzględnia znaku null jako znaku \0
zerowego i wyszukiwania językowego na platformie .NET Core 3.1 i wcześniejsze lokalizuje znak na pozycji 3. Wyszukiwanie porządkowe znajduje znak na pozycji 3 we wszystkich wersjach platformy .NET.
Reguły analizy kodu CA1307 można uruchomić: Określ wartość StringComparison dla jasności i CA1309: Użyj porządkowego ciąguComparison , aby znaleźć witryny wywołań w kodzie, w którym porównanie ciągów nie jest określone lub nie jest porządkowe.
Aby uzyskać więcej informacji, zobacz Zmiany zachowania podczas porównywania ciągów na platformie .NET 5+.
String.EndsWith
const string foo = "abc";
Console.WriteLine(foo.EndsWith("\0"));
Console.WriteLine(foo.EndsWith("c"));
Console.WriteLine(foo.EndsWith("\0", StringComparison.CurrentCulture));
Console.WriteLine(foo.EndsWith("\0", StringComparison.Ordinal));
Console.WriteLine(foo.EndsWith('\0'));
Ważne
W programie .NET 5+ uruchomionym w wersjach systemu Windows wymienionych w tabeli ICU w systemie Windows powyższe wydruki fragmentu kodu:
True
True
True
False
False
Aby uniknąć tego zachowania, użyj przeciążenia parametru char
lub StringComparison.Oridinal
.
String.StartsWith
const string foo = "abc";
Console.WriteLine(foo.StartsWith("\0"));
Console.WriteLine(foo.StartsWith("a"));
Console.WriteLine(foo.StartsWith("\0", StringComparison.CurrentCulture));
Console.WriteLine(foo.StartsWith("\0", StringComparison.Ordinal));
Console.WriteLine(foo.StartsWith('\0'));
Ważne
W programie .NET 5+ uruchomionym w wersjach systemu Windows wymienionych w tabeli ICU w systemie Windows powyższe wydruki fragmentu kodu:
True
True
True
False
False
Aby uniknąć tego zachowania, użyj przeciążenia parametru char
lub StringComparison.Oridinal
.
TimeZoneInfo.FindSystemTimeZoneById
Funkcja ICU zapewnia elastyczność tworzenia TimeZoneInfo wystąpień przy użyciu identyfikatorów stref czasowych IANA , nawet jeśli aplikacja jest uruchomiona w systemie Windows. Podobnie można tworzyć TimeZoneInfo wystąpienia z identyfikatorami stref czasowych systemu Windows, nawet w przypadku uruchamiania na platformach innych niż Windows. Należy jednak pamiętać, że ta funkcja nie jest dostępna w przypadku korzystania z trybu NLS lub niezmiennego trybu globalizacji.
Interfejsy API zależne od ICU
Platforma .NET wprowadziła interfejsy API zależne od ICU. Te interfejsy API mogą zakończyć się powodzeniem tylko w przypadku korzystania z ICU. Oto kilka przykładów:
W wersjach systemu Windows wymienionych w tabeli sekcji ICU w systemie Windows wymienione interfejsy API będą konsekwentnie kończyć się powodzeniem. Jednak w starszych wersjach systemu Windows te interfejsy API stale kończą się niepowodzeniem. W takich przypadkach można włączyć funkcję ICU app-local, aby zapewnić powodzenie tych interfejsów API. Na platformach innych niż Windows te interfejsy API zawsze powiedzą się niezależnie od wersji.
Ponadto niezwykle ważne jest, aby aplikacje miały pewność, że nie działają w trybie globalizacji lub w trybie NLS, aby zagwarantować sukces tych interfejsów API.
Używanie nls zamiast ICU
Użycie ICU zamiast NLS może spowodować różnice behawioralne w niektórych operacjach związanych z globalizacją. Aby powrócić do korzystania z równoważenia obciążenia sieciowego, deweloper może zrezygnować z implementacji ICU. Aplikacje mogą włączyć tryb NLS na dowolny z następujących sposobów:
W pliku projektu:
<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" /> </ItemGroup>
W pliku
runtimeconfig.json
:{ "runtimeOptions": { "configProperties": { "System.Globalization.UseNls": true } } }
Ustawiając zmienną środowiskową
DOTNET_SYSTEM_GLOBALIZATION_USENLS
na wartośćtrue
lub1
.
Uwaga
Wartość ustawiona w projekcie lub w runtimeconfig.json
pliku ma pierwszeństwo przed zmienną środowiskową.
Aby uzyskać więcej informacji, zobacz Ustawienia konfiguracji środowiska uruchomieniowego.
Określanie, czy aplikacja korzysta z interfejsu ICU
Poniższy fragment kodu może pomóc w ustaleniu, czy aplikacja jest uruchomiona z bibliotekami ICU (a nie NLS).
public static bool ICUMode()
{
SortVersion sortVersion = CultureInfo.InvariantCulture.CompareInfo.Version;
byte[] bytes = sortVersion.SortId.ToByteArray();
int version = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0];
return version != 0 && version == sortVersion.FullVersion;
}
Aby określić wersję platformy .NET, użyj polecenia RuntimeInformation.FrameworkDescription.
Lokalne ICU aplikacji
Każda wersja ICU może przynieść z nim poprawki błędów, a także zaktualizowane dane repozytorium Common Locale Data Repository (CLDR), które opisują języki świata. Przenoszenie między wersjami ICU może subtelnie wpływać na zachowanie aplikacji, jeśli chodzi o operacje związane z globalizacją. Aby ułatwić deweloperom aplikacji zapewnienie spójności we wszystkich wdrożeniach, platforma .NET 5 i nowsze wersje umożliwiają aplikacjom zarówno w systemach Windows, jak i Unix przenoszenie i używanie własnej kopii ICU.
Aplikacje mogą korzystać z lokalnego trybu implementacji ICU aplikacji w jeden z następujących sposobów:
W pliku projektu ustaw odpowiednią
RuntimeHostConfigurationOption
wartość:<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<suffix>:<version> or <version>" /> </ItemGroup>
Lub w pliku runtimeconfig.json ustaw odpowiednią
runtimeOptions.configProperties
wartość:{ "runtimeOptions": { "configProperties": { "System.Globalization.AppLocalIcu": "<suffix>:<version> or <version>" } } }
Możesz też ustawić zmienną środowiskową
DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU
na wartość<suffix>:<version>
lub<version>
.<suffix>
: Opcjonalny sufiks o długości mniejszej niż 36 znaków, zgodnie z publiczną konwencją pakowania ICU. Podczas tworzenia niestandardowego ICU można dostosować go w celu utworzenia nazw lib i wyeksportowanych nazw symboli, aby zawierał sufiks, na przykładlibicuucmyapp
, gdziemyapp
jest sufiksem.<version>
: Prawidłowa wersja ICU, na przykład 67.1. Ta wersja służy do ładowania plików binarnych i pobierania wyeksportowanych symboli.
Po ustawieniu jednej z tych opcji możesz dodać do projektu plik Microsoft.ICU.ICU4C.RuntimePackageReference
odpowiadający skonfigurowanemu version
elementowi i wszystkiemu, co jest potrzebne.
Alternatywnie, aby załadować ICU po ustawieniu przełącznika lokalnego aplikacji, platforma .NET używa NativeLibrary.TryLoad metody , która sonduje wiele ścieżek. Metoda najpierw próbuje znaleźć bibliotekę we NATIVE_DLL_SEARCH_DIRECTORIES
właściwości , która jest tworzona przez hosta dotnet na deps.json
podstawie pliku dla aplikacji. Aby uzyskać więcej informacji, zobacz Domyślne sondowanie.
W przypadku aplikacji samodzielnie użytkownik nie wymaga żadnej specjalnej akcji, innej niż upewnienie się, że ICU znajduje się w katalogu aplikacji (w przypadku aplikacji samodzielnie katalog roboczy domyślnie ma wartość NATIVE_DLL_SEARCH_DIRECTORIES
).
Jeśli używasz ICU za pośrednictwem pakietu NuGet, działa to w aplikacjach zależnych od platformy. Narzędzie NuGet rozpoznaje zasoby natywne i uwzględnia je w deps.json
pliku i w katalogu wyjściowym aplikacji w runtimes
katalogu . Platforma .NET ładuje go stamtąd.
W przypadku aplikacji zależnych od platformy (nie samodzielnych), w przypadku których iCU jest używane z kompilacji lokalnej, należy wykonać dodatkowe kroki. Zestaw .NET SDK nie ma jeszcze funkcji dla "luźnych" natywnych plików binarnych do włączenia deps.json
do (zobacz ten problem z zestawem SDK). Zamiast tego można to włączyć, dodając dodatkowe informacje do pliku projektu aplikacji. Na przykład:
<ItemGroup>
<IcuAssemblies Include="icu\*.so*" />
<RuntimeTargetsCopyLocalItems Include="@(IcuAssemblies)" AssetType="native" CopyLocal="true"
DestinationSubDirectory="runtimes/linux-x64/native/" DestinationSubPath="%(FileName)%(Extension)"
RuntimeIdentifier="linux-x64" NuGetPackageId="System.Private.Runtime.UnicodeData" />
</ItemGroup>
Należy to zrobić dla wszystkich plików binarnych ICU dla obsługiwanych środowisk uruchomieniowych. NuGetPackageId
Ponadto metadane w RuntimeTargetsCopyLocalItems
grupie elementów muszą być zgodne z pakietem NuGet, do którego rzeczywiście odwołuje się projekt.
Zachowanie systemu macOS
System macOS ma inne zachowanie w przypadku rozpoznawania zależnych bibliotek dynamicznych z poleceń ładowania określonych w Mach-O
pliku niż moduł ładujący systemu Linux. W module ładujący systemu Linux platforma .NET może wypróbować libicudata
element , libicuuc
i libicui18n
(w tej kolejności), aby spełnić graf zależności ICU. Jednak w systemie macOS nie działa to. Podczas kompilowania funkcji ICU w systemie macOS domyślnie uzyskujesz bibliotekę dynamiczną z tymi poleceniami ładowania w systemie libicuuc
. Poniższy fragment kodu przedstawia przykład.
~/ % otool -L /Users/santifdezm/repos/icu-build/icu/install/lib/libicuuc.67.1.dylib
/Users/santifdezm/repos/icu-build/icu/install/lib/libicuuc.67.1.dylib:
libicuuc.67.dylib (compatibility version 67.0.0, current version 67.1.0)
libicudata.67.dylib (compatibility version 67.0.0, current version 67.1.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0)
Te polecenia odwołują się tylko do nazwy bibliotek zależnych dla innych składników ICU. Moduł ładujący wykonuje wyszukiwanie zgodnie dlopen
z konwencjami, które obejmują posiadanie tych bibliotek w katalogach systemowych lub ustawienie LD_LIBRARY_PATH
wariantów env lub posiadanie ICU w katalogu na poziomie aplikacji. Jeśli nie możesz ustawić LD_LIBRARY_PATH
ani upewnić się, że pliki binarne ICU znajdują się w katalogu na poziomie aplikacji, musisz wykonać dodatkową pracę.
Istnieją pewne dyrektywy modułu ładującego, takie jak @loader_path
, które informują moduł ładujący o wyszukiwaniu tej zależności w tym samym katalogu co plik binarny za pomocą tego polecenia ładowania. Istnieją dwa sposoby osiągnięcia tego celu:
install_name_tool -change
Uruchom następujące polecenia:
install_name_tool -change "libicudata.67.dylib" "@loader_path/libicudata.67.dylib" /path/to/libicuuc.67.1.dylib install_name_tool -change "libicudata.67.dylib" "@loader_path/libicudata.67.dylib" /path/to/libicui18n.67.1.dylib install_name_tool -change "libicuuc.67.dylib" "@loader_path/libicuuc.67.dylib" /path/to/libicui18n.67.1.dylib
Stosowanie poprawek ICU w celu utworzenia nazw instalacji za pomocą polecenia
@loader_path
Przed uruchomieniem autokonf (
./runConfigureICU
) zmień następujące wiersze na:LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name @loader_path/$(notdir $(MIDDLE_SO_TARGET))
ICU w usłudze WebAssembly
Dostępna jest wersja ICU przeznaczona specjalnie dla obciążeń zestawu WebAssembly. Ta wersja zapewnia zgodność globalizacji z profilami komputerów stacjonarnych. Aby zmniejszyć rozmiar pliku danych ICU z 24 MB do 1,4 MB (lub ~0,3 MB, jeśli jest skompresowany z Brotli), to obciążenie ma kilka ograniczeń.
Następujące interfejsy API nie są obsługiwane:
- CultureInfo.EnglishName
- CultureInfo.NativeName
- DateTimeFormatInfo.NativeCalendarName
- RegionInfo.NativeName
Następujące interfejsy API są obsługiwane z ograniczeniami:
- String.Normalize(NormalizationForm) i String.IsNormalized(NormalizationForm) nie obsługują rzadko używanych FormKC i FormKD formularzy.
- RegionInfo.CurrencyNativeName Zwraca tę samą wartość co RegionInfo.CurrencyEnglishName.
Ponadto obsługiwana jest mniejsza liczba ustawień regionalnych. Listę obsługiwanych można znaleźć w repozytorium dotnet/icu.
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla