Globalizace .NET a ICU
Před .NET 5 používala rozhraní API globalizace .NET různé podkladové knihovny na různých platformách. V unixu používala rozhraní API mezinárodní komponenty pro kódování Unicode (ICU) a ve Windows používala podporu národních jazyků (NLS). Výsledkem jsou některé rozdíly v chování několika rozhraní API globalizace při spouštění aplikací na různých platformách. Rozdíly v chování byly zřejmé v těchto oblastech:
- Jazykové verze a data jazykové verze
- Velikost písmen řetězců
- Řazení a vyhledávání řetězců
- Řazení klíčů
- Normalizace řetězců
- Podpora internationalizovaných názvů domén (IDN)
- Zobrazovaný název časového pásma v Linuxu
Počínaje platformou .NET 5 mají vývojáři větší kontrolu nad tím, jakou podkladovou knihovnu používáte, což umožňuje aplikacím vyhnout se rozdílům na různých platformách.
Poznámka:
Data jazykové verze, která řídí chování knihovny ICU, se obvykle spravují úložištěm CLDR (Common Locale Data Repository), nikoli modulem runtime.
ICU ve Windows
Systém Windows teď obsahuje předinstalovanou verzi icu.dll jako součást svých funkcí, které se automaticky používají pro úlohy globalizace. Tato úprava umožňuje rozhraní .NET používat tuto knihovnu ICU pro podporu globalizace. V případech, kdy je knihovna ICU nedostupná nebo nejde načíst, stejně jako u starších verzí Windows, .NET 5 a dalších verzí se vrátí k použití implementace založené na službě NLS.
Následující tabulka uvádí, které verze rozhraní .NET dokážou načíst knihovnu ICU napříč různými verzemi klienta a serveru Windows:
Verze .NET | Verze Windows |
---|---|
.NET 5 nebo .NET 6 | Klient Windows 10 verze 1903 nebo novější |
.NET 5 nebo .NET 6 | Windows Server 2022 nebo novější |
.NET 7 nebo novější | Klient Windows 10 verze 1703 nebo novější |
.NET 7 nebo novější | Windows Server 2019 nebo novější |
Poznámka:
.NET 7 a novější verze mají možnost načíst ICU ve starších verzích Windows na rozdíl od .NET 6 a .NET 5.
Poznámka:
I když používáte ICU, CurrentCulture
CurrentUICulture
a CurrentRegion
členové stále používají rozhraní API operačního systému Windows k zachování uživatelských nastavení.
Rozdíly v chování
Pokud aplikaci upgradujete tak, aby cílila na .NET 5 nebo novější, můžou se v aplikaci zobrazit změny, i když si neuvědomíte, že používáte zařízení globalizace. V následující části najdete některé změny chování, ke které může dojít.
Řazení řetězců a System.Globalization.CompareOptions
CompareOptions
je výčet možností, které lze předat, aby String.Compare
ovlivnily porovnání dvou řetězců.
Porovnání řetězců pro rovnost a určení jejich pořadí řazení se liší mezi NLS a ICU. Zejména jde o toto:
- Výchozí pořadí řazení řetězců se liší, takže to bude zřejmé i v případě, že ho nepoužíváte
CompareOptions
přímo. Při použití ICU seNone
výchozí možnost provede stejně jakoStringSort
.StringSort
seřadí jiné než alfanumerické znaky před alfanumerickými znaky (například "faktura" seřadí před "faktury"). Pokud chcete obnovit předchozíNone
funkce, musíte použít implementaci založenou na vyrovnávání zatížení sítě. - Výchozí zpracování znaků ligatury se liší. V rámci NLS se ligatury a jejich protějšky bez ligatur (například "oeuf" a "ğuf") považují za stejné, ale nejedná se o případ ICU v .NET. Důvodem je jiná síla kolace mezi dvěma implementacemi. Pokud chcete obnovit chování nlS při použití ICU, použijte
CompareOptions.IgnoreNonSpace
hodnotu.
String.IndexOf
Představte si následující kód, který volá String.IndexOf(String) vyhledání indexu znaku \0
null v řetězci.
const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.CurrentCulture)}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.Ordinal)}");
- V .NET Core 3.1 a starších verzích ve Windows se fragment kódu vytiskne
3
na každý ze tří řádků. - V případě .NET 5 a novějších verzí spuštěných ve verzích Windows uvedených v tabulce oddílů ICU v systému Windows se fragment kódu vytiskne
0
0
a3
(pro řadové vyhledávání).
Ve výchozím nastavení String.IndexOf(String) provede lingvistické vyhledávání pracující s jazykovou verzí. ICU považuje znak null za znak \0
nulové hmotnosti, a proto se v řetězci při použití lingvistického vyhledávání v .NET 5 a novějším nenajde. Vyrovnávání zatížení sítě však nepovažuje znak null za znak \0
nulové hmotnosti a lingvistické vyhledávání v .NET Core 3.1 a starší vyhledá znak na pozici 3. Řadové vyhledávání najde znak na pozici 3 ve všech verzích .NET.
Pravidla analýzy kódu CA1307: Zadejte StringComparison pro přehlednost a CA1309: Pomocí řadového StringComparisonu vyhledejte weby volání ve vašem kódu, kde není zadané porovnání řetězců nebo není pořadové.
Další informace najdete v tématu Změny chování při porovnávání řetězců v .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'));
Důležité
V .NET 5 a novějších spuštěných ve verzích Windows uvedených v tabulce ICU ve Windows se vytiskne předchozí fragment kódu:
True
True
True
False
False
Chcete-li se tomuto chování vyhnout, použijte přetížení parametru char
nebo 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'));
Důležité
V .NET 5 a novějších spuštěných ve verzích Windows uvedených v tabulce ICU ve Windows se vytiskne předchozí fragment kódu:
True
True
True
False
False
Chcete-li se tomuto chování vyhnout, použijte přetížení parametru char
nebo StringComparison.Ordinal
.
TimeZoneInfo.FindSystemTimeZoneById
ICU poskytuje flexibilitu při vytváření TimeZoneInfo instancí pomocí ID časových pásem IANA , a to i v případě, že aplikace běží ve Windows. Podobně můžete vytvářet TimeZoneInfo instance s ID časových pásem Windows, i když běží na jiných platformách než Windows. Je však důležité si uvědomit, že tato funkce není dostupná při použití režimu nlS nebo globalizace invariantní režim.
Zkratky dnů v týdnu
Metoda DateTimeFormatInfo.GetShortestDayName(DayOfWeek) získá nejkratší zkrácený název dne pro zadaný den v týdnu.
- V .NET Core 3.1 a starších verzích ve Windows se tyto zkratky v týdnu skládají ze dvou znaků, například "Su".
- V .NET 5 a novějších verzích se tyto zkratky dnů v týdnu skládají jenom z jednoho znaku, například "S".
Rozhraní API závislá na ICU
Rozhraní .NET zavedla rozhraní API, která jsou závislá na ICU. Tato rozhraní API můžou být úspěšná pouze při použití ICU. Několik příkladů:
Ve verzích Windows uvedených v tabulce oddílů ICU ve Windows jsou uvedená rozhraní API úspěšná. Ve starších verzích Windows však tato rozhraní API selžou. V takových případech můžete povolit funkci ICU místní aplikace, abyste zajistili úspěch těchto rozhraní API. Na jiných platformách než Windows jsou tato rozhraní API vždy úspěšná bez ohledu na verzi.
Kromě toho je pro aplikace důležité zajistit, aby neběží v globalizačním režimu invariantního režimu nebo v režimu NLS, aby se zajistil úspěch těchto rozhraní API.
Místo ICU použijte službu NLS.
Použití ICU místo NLS může vést k rozdílům v chování některých operací souvisejících s globalizací. Pokud se chcete vrátit zpět k používání nlS, můžete vyjádřit výslovný nesouhlas s implementací ICU. Aplikace můžou povolit režim služby NLS některým z následujících způsobů:
V souboru projektu:
<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" /> </ItemGroup>
V souboru
runtimeconfig.json
:{ "runtimeOptions": { "configProperties": { "System.Globalization.UseNls": true } } }
Nastavením proměnné
DOTNET_SYSTEM_GLOBALIZATION_USENLS
prostředí na hodnotutrue
nebo1
.
Poznámka:
Hodnota nastavená v projektu nebo v runtimeconfig.json
souboru má přednost před proměnnou prostředí.
Další informace naleznete v tématu Nastavení konfigurace modulu runtime.
Určení, jestli vaše aplikace používá ICU
Následující fragment kódu vám může pomoct určit, jestli je vaše aplikace spuštěná s knihovnami ICU (a ne 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;
}
K určení verze rozhraní .NET použijte RuntimeInformation.FrameworkDescription.
Místní ICU pro aplikace
Každá verze ICU může obsahovat opravy chyb a aktualizovaná data cLDR (Common Locale Data Repository), která popisují jazyky světa. Přechod mezi verzemi ICU může subtálně ovlivnit chování aplikace, pokud jde o operace související s globalizací. Aby vývojáři aplikací zajistili konzistenci napříč všemi nasazeními, umožňují .NET 5 a novějším verzím aplikacím ve Windows i Unixu přenášet a používat vlastní kopii ICU.
Aplikace se můžou přihlásit k režimu implementace ICU v místní aplikaci jedním z následujících způsobů:
V souboru projektu nastavte odpovídající
RuntimeHostConfigurationOption
hodnotu:<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<suffix>:<version> or <version>" /> </ItemGroup>
Nebo v souboru runtimeconfig.json nastavte odpovídající
runtimeOptions.configProperties
hodnotu:{ "runtimeOptions": { "configProperties": { "System.Globalization.AppLocalIcu": "<suffix>:<version> or <version>" } } }
Nebo nastavením proměnné
DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU
prostředí na hodnotu<suffix>:<version>
nebo<version>
.<suffix>
: Volitelná přípona kratší než 36 znaků, která následuje po konvencích balení veřejných jednotek ICU. Při vytváření vlastní jednotky ICU ji můžete přizpůsobit tak, aby vznikly názvy libů a exportované názvy symbolů, které budou obsahovat příponu,libicuucmyapp
například , kdemyapp
je přípona.<version>
: Platná verze ICU, například 67.1. Tato verze slouží k načtení binárních souborů a získání exportovaných symbolů.
Pokud je některý z těchto možností nastavený, můžete do projektu přidat Microsoft.ICU.ICU4C.RuntimePackageReference
, který odpovídá nakonfigurované version
a to vše, co je potřeba.
Pokud chcete načíst ICU, když je nastavený místní přepínač aplikace, použije .NET metodu NativeLibrary.TryLoad , která testuje více cest. Metoda se nejprve pokusí najít knihovnu NATIVE_DLL_SEARCH_DIRECTORIES
ve vlastnosti, která je vytvořena hostitelem dotnet na deps.json
základě souboru aplikace. Další informace najdete v tématu Výchozí zkušební verze.
V případě samostatných aplikací nevyžaduje uživatel žádnou zvláštní akci, kromě zajištění, že je ICU v adresáři aplikace (u samostatných aplikací je výchozí NATIVE_DLL_SEARCH_DIRECTORIES
pracovní adresář).
Pokud icU využíváte prostřednictvím balíčku NuGet, funguje to v aplikacích závislých na architektuře. NuGet vyřeší nativní prostředky a zahrne je do deps.json
souboru a do výstupního adresáře pro aplikaci v rámci runtimes
adresáře. .NET ho odtud načte.
U aplikací závislých na architektuře (ne v samostatném prostředí), které spotřebovávají JEDNOTKY ICU z místního sestavení, musíte provést další kroky. Sada .NET SDK zatím nemá funkci pro "volné" nativní binární soubory, do deps.json
kterých se mají začlenit (viz tento problém se sadou SDK). Místo toho to můžete povolit přidáním dalších informací do souboru projektu aplikace. Příklad:
<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>
To se musí provést pro všechny binární soubory ICU pro podporované moduly runtime. NuGetPackageId
Metadata ve RuntimeTargetsCopyLocalItems
skupině položek musí také odpovídat balíčku NuGet, na který projekt ve skutečnosti odkazuje.
Chování macOS
macOS má jiné chování při překladu závislých dynamických knihoven z příkazů pro načtení zadaných v Mach-O
souboru, než je zavaděč Linuxu. V zavaděče Linuxu může .NET vyzkoušet libicudata
libicuuc
, a libicui18n
(v takovém pořadí) splňovat graf závislostí ICU. V systému macOS to ale nefunguje. Při vytváření ICU v systému macOS ve výchozím nastavení získáte dynamickou knihovnu s těmito příkazy pro načtení v libicuuc
. Následující fragment kódu ukazuje příklad.
~/ % 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)
Tyto příkazy pouze odkazují na název závislých knihoven pro ostatní komponenty ICU. Zavaděč provádí vyhledávání podle dlopen
konvencí, které zahrnují použití těchto knihoven v systémových adresářích nebo nastavení LD_LIBRARY_PATH
hodnot env vars nebo s ICU v adresáři na úrovni aplikace. Pokud nemůžete nastavit LD_LIBRARY_PATH
nebo zajistit, aby binární soubory ICU byly v adresáři na úrovni aplikace, budete muset udělat další práci.
Pro zavaděč existují určité direktivy, například @loader_path
, které zavaděče říkají, že má vyhledat danou závislost ve stejném adresáři jako binární soubor s tímto příkazem pro načtení. Existují dva způsoby, jak toho dosáhnout:
install_name_tool -change
Spusťte následující příkazy:
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
Oprava ICU pro vytvoření názvů instalací pomocí
@loader_path
Před spuštěním příkazu autoconf (
./runConfigureICU
) změňte tyto řádky 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 na WebAssembly
K dispozici je verze ICU určená speciálně pro úlohy WebAssembly. Tato verze poskytuje kompatibilitu globalizace s profily plochy. Pokud chcete zmenšit velikost datového souboru ICU z 24 MB na 1,4 MB (nebo ~0,3 MB v případě komprimace pomocí Brotli), má tato úloha několik omezení.
Následující rozhraní API nejsou podporována:
- CultureInfo.EnglishName
- CultureInfo.NativeName
- DateTimeFormatInfo.NativeCalendarName
- RegionInfo.NativeName
Následující rozhraní API jsou podporována s omezeními:
- String.Normalize(NormalizationForm) a String.IsNormalized(NormalizationForm) nepodporuje zřídka používané FormKC formuláře a FormKD formuláře.
- RegionInfo.CurrencyNativeName vrátí stejnou hodnotu jako RegionInfo.CurrencyEnglishName.
Kromě toho se podporuje méně národních prostředí. Podporovaný seznam najdete v úložišti dotnet/icu.