.NET-globalizáció és ICU
A .NET 5 előtt a .NET globalizációs API-k különböző mögöttes kódtárakat használtak különböző platformokon. A Unix-on az API-k a Unicode (ICU) nemzetközi összetevőit használták, Windowson pedig a Nemzeti nyelvi támogatást (NLS) használták. Ez viselkedésbeli különbségeket eredményezett néhány globalizációs API-ban, amikor különböző platformokon futtat alkalmazásokat. A viselkedésbeli különbségek az alábbi területeken voltak nyilvánvalóak:
- Kultúrák és kulturális adatok
- Sztringház
- Sztring rendezés és keresés
- Kulcsok rendezése
- Sztring normalizálása
- Nemzetközi tartománynevek (IDN) támogatása
- Időzóna megjelenítendő neve Linuxon
A .NET 5-től kezdve a fejlesztők nagyobb mértékben szabályozják a mögöttes kódtár használatát, így az alkalmazások elkerülhetik a platformok közötti különbségeket.
Feljegyzés
Az ICU-kódtár viselkedését meghatározó kulturális adatokat általában a Common Locale Data Repository (CLDR) kezeli, nem pedig a futtatókörnyezet.
ICU Windows rendszeren
A Windows mostantól beépített egy előre telepített icu.dll verziót a globalizációs feladatokhoz automatikusan alkalmazott funkciók részeként. Ez a módosítás lehetővé teszi, hogy a .NET ezt az ICU-kódtárat használja a globalizációs támogatásához. Azokban az esetekben, amikor az ICU-kódtár nem érhető el, vagy nem tölthető be, ahogyan a régebbi Windows-verziók esetében, a .NET 5 és az azt követő verziók is visszaállnak az NLS-alapú implementáció használatára.
Az alábbi táblázat azt mutatja be, hogy a .NET mely verziói képesek betölteni az ICU-kódtárat különböző Windows-ügyfél- és kiszolgálóverziókra:
.NET-verzió | Windows-verzió |
---|---|
.NET 5 vagy .NET 6 | Windows-ügyfél 10 1903-es vagy újabb verziója |
.NET 5 vagy .NET 6 | Windows Server 2022 vagy újabb |
.NET 7 vagy újabb | Windows-ügyfél 10 1703-es vagy újabb verziója |
.NET 7 vagy újabb | Windows Server 2019 vagy újabb |
Feljegyzés
A .NET 7 és újabb verziók képesek betölteni az ICU-t a régebbi Windows-verziókra, szemben a .NET 6 és a .NET 5 verzióval.
Feljegyzés
Még az ICU használata esetén is a CurrentCulture
windowsos CurrentRegion
CurrentUICulture
operációs rendszer API-jait használják a felhasználói beállítások betartásához.
Viselkedésbeli különbségek
Ha az alkalmazást a .NET 5-ös vagy újabb verziójára frissíti, akkor is megjelennek a változások az alkalmazásban, ha nem veszi észre, hogy globalizációs létesítményeket használ. Az alábbi szakasz felsorol néhány viselkedési változást, amelyekben esetleg tapasztalhat.
Sztring rendezés és System.Globalization.CompareOptions
CompareOptions
a két sztring összehasonlításának befolyásolása érdekében String.Compare
átadott lehetőségek számbavétele.
Az egyenlőség sztringjeinek összehasonlítása és rendezési sorrendjük meghatározása különbözik az NLS és az ICU között. Elsősorban:
- Az alapértelmezett sztring rendezési sorrendje eltérő, így ez akkor is látható lesz, ha nem közvetlenül használja
CompareOptions
. Az ICU használata esetén azNone
alapértelmezett beállítás ugyanúgy teljesít, mintStringSort
a .StringSort
a nem alfanumerikus karaktereket az alfanumerikus karakterek elé rendezi (így például a "számla" a "számlák" elé rendezi). Az előzőNone
funkció visszaállításához az NLS-alapú implementációt kell használnia. - A ligatúrakarakterek alapértelmezett kezelése eltérő. Az NLS alatt a ligatúrák és azok nem ligatúrás megfelelői (például az "oeuf" és az "œuf") egyenlőnek minősülnek, de a .NET-ben az ICU esetében ez nem így van. Ennek oka, hogy a két implementáció eltérő rendezési erőssége van. Ha vissza szeretné állítani az NLS viselkedését az ICU használatakor, használja az
CompareOptions.IgnoreNonSpace
értéket.
String.IndexOf
Fontolja meg a következő kódot, amely meghívja String.IndexOf(String) a null karakter \0
indexének megkeresését egy sztringben.
const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.CurrentCulture)}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.Ordinal)}");
- A Windows .NET Core 3.1 és korábbi verzióiban a kódrészlet a három sor mindegyikére nyomtat
3
. - A Windows szakasztábla ICU-jában felsorolt Windows-verziókon futó .NET 5-ös és újabb verziók esetén a kódrészlet kinyomtatja
0
0
és3
(a sorszám szerinti kereséshez) nyomtatja.
Alapértelmezés szerint String.IndexOf(String) egy kultúratudatos nyelvi keresést hajt végre. Az ICU nulla súlyú karakternek tekinti a null karaktert \0
, így a karakter nem található meg a sztringben a .NET 5-ös és újabb verzióiban végzett nyelvi keresés során. Az NLS azonban nem tekinti a null karaktert \0
nulla súlyú karakternek, és a .NET Core 3.1 és korábbi verzióiban a nyelvi keresés a 3. pozícióban találja meg a karaktert. Az ordinális keresés az összes .NET-verzióban a 3. pozícióban találja meg a karaktert.
Futtathatja a CA1307 kódelemzési szabályokat : A StringComparison megadása az egyértelműség érdekében és a CA1309: Az ordinal StringComparison használatával megkeresheti a kód azon hívási helyeit, ahol a sztring-összehasonlítás nincs megadva, vagy nincs sorszáma.
További információ: Viselkedésváltozások a .NET 5+ sztringjeinek összehasonlítása során.
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'));
Fontos
A Windows-tábla ICU-jában felsorolt Windows-verziókon futó .NET 5+-ban az előző kódrészlet a következőt nyomtatja:
True
True
True
False
False
Ennek a viselkedésnek a elkerülése érdekében használja a char
paraméter túlterhelését vagy 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'));
Fontos
A Windows-tábla ICU-jában felsorolt Windows-verziókon futó .NET 5+-ban az előző kódrészlet a következőt nyomtatja:
True
True
True
False
False
Ennek a viselkedésnek a elkerülése érdekében használja a char
paraméter túlterhelését vagy StringComparison.Ordinal
.
TimeZoneInfo.FindSystemTimeZoneById
Az ICU rugalmasan hozhat létre TimeZoneInfo példányokat IANA időzóna-azonosítók használatával, még akkor is, ha az alkalmazás Windows rendszeren fut. Hasonlóképpen windowsos időzóna-azonosítókkal is létrehozhat TimeZoneInfo példányokat, még akkor is, ha nem Windows-platformokon fut. Fontos azonban megjegyezni, hogy ez a funkció nem érhető el NLS mód vagy globalizációs invariáns mód használatakor.
Hét napjának rövidítései
A DateTimeFormatInfo.GetShortestDayName(DayOfWeek) metódus a hét megadott napjának legrövidebb rövidített nevét szerzi be.
- A Windows .NET Core 3.1-es és korábbi verzióiban ezek a hétközi rövidítések két karakterből álltak, például "Su".
- A .NET 5-ös és újabb verzióiban ezek a hétköznapok rövidítései csak egy karakterből állnak, például "S".
ICU-függő API-k
A .NET olyan API-kat vezetett be, amelyek az intenzív osztálytól függenek. Ezek az API-k csak az intenzív osztály használata esetén lehetnek sikeresek. Íme néhány példa:
A Windows szakasztábla ICU-jában felsorolt Windows-verziókon az említett API-k sikeresek. A Windows régebbi verzióiban azonban ezek az API-k sikertelenek. Ilyen esetekben engedélyezheti az alkalmazás helyi ICU funkcióját ezeknek az API-knak a sikerességéhez. Nem Windows-platformokon ezek az API-k a verziótól függetlenül mindig sikeresek.
Emellett elengedhetetlen, hogy az alkalmazások ne globalizálási invariáns módban vagy NLS módban fussanak, hogy garantálják ezeknek az API-knak a sikerességét.
NLS használata az ICU helyett
Az NLS helyett az ICU használata viselkedési különbségeket eredményezhet néhány globalizációval kapcsolatos művelettel kapcsolatban. Ha vissza szeretne térni az NLS használatára, letilthatja az ICU-implementációt. Az alkalmazások az alábbi módokon engedélyezhetik az NLS-módot:
A projektfájlban:
<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" /> </ItemGroup>
Az
runtimeconfig.json
fájlban:{ "runtimeOptions": { "configProperties": { "System.Globalization.UseNls": true } } }
A környezeti változó
DOTNET_SYSTEM_GLOBALIZATION_USENLS
értékretrue
vagy1
értékre állításával.
Feljegyzés
A projektben vagy a runtimeconfig.json
fájlban beállított érték elsőbbséget élvez a környezeti változóval szemben.
További információ: Futtatókörnyezet konfigurációs beállításai.
Annak megállapítása, hogy az alkalmazás ICU-t használ-e
Az alábbi kódrészlet segíthet megállapítani, hogy az alkalmazás ICU-kódtárakkal (és nem NLS-ekkel) fut-e.
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;
}
A .NET verziójának meghatározásához használja a .NET-et RuntimeInformation.FrameworkDescription.
Alkalmazás helyi ICU-ja
Az ICU minden egyes kiadása hibajavításokat és frissített Common Locale Data Repository (CLDR) adatokat hozhat magával, amelyek a világ nyelveit írják le. Az ICU verziói közötti váltás kis mértékben befolyásolhatja az alkalmazás viselkedését a globalizációval kapcsolatos műveletek esetén. Annak érdekében, hogy az alkalmazásfejlesztők biztosíthassák az összes üzemelő példány konzisztenciáját, a .NET 5-ös és újabb verziói lehetővé teszik, hogy a Windows és a Unix rendszeren futó alkalmazások saját ICU-példányt hordozhassanak és használjanak.
Az alkalmazások az alábbi módokon választhatják az alkalmazás helyi ICU implementálási módját:
A projektfájlban adja meg a megfelelő
RuntimeHostConfigurationOption
értéket:<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<suffix>:<version> or <version>" /> </ItemGroup>
Vagy a runtimeconfig.json fájlban állítsa be a megfelelő
runtimeOptions.configProperties
értéket:{ "runtimeOptions": { "configProperties": { "System.Globalization.AppLocalIcu": "<suffix>:<version> or <version>" } } }
Vagy a környezeti változó
DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU
értékre<suffix>:<version>
vagy<version>
értékre állításával.<suffix>
: 36 karakternél rövidebb opcionális utótag, a nyilvános ICU csomagolási konvenciók szerint. Egyéni intenzív osztály létrehozásakor testre szabhatja, hogy a lib neveket és az exportált szimbólumneveket úgy hozza létre, hogy egy utótagot tartalmazzon, például azt,libicuucmyapp
hogy holmyapp
az utótag.<version>
: Érvényes ICU-verzió, például 67.1. Ez a verzió a bináris fájlok betöltésére és az exportált szimbólumok lekérésére szolgál.
Ha valamelyik beállítás be van állítva, hozzáadhat egy Microsoft.ICU.ICU4C.Runtime-t PackageReference
a projekthez, amely megfelel a konfiguráltnak version
, és ez minden, amire szükség van.
Azt is megteheti, hogy az alkalmazás helyi kapcsolójának beállításakor be szeretné tölteni az intenzív osztályt, a .NET a NativeLibrary.TryLoad metódust használja, amely több elérési utat is megvizsgál. A metódus először megpróbálja megkeresni a NATIVE_DLL_SEARCH_DIRECTORIES
tulajdonság tárát, amelyet a dotnet-gazdagép hoz létre az deps.json
alkalmazás fájlja alapján. További információ: Alapértelmezett próbaidő.
Önálló alkalmazások esetén a felhasználó nem igényel speciális műveletet, kivéve, ha meggyőződik arról, hogy az ICU az alkalmazáskönyvtárban van (önálló alkalmazások esetén a munkakönyvtár alapértelmezés szerint NATIVE_DLL_SEARCH_DIRECTORIES
a következő).
Ha az intenzív osztályt NuGet-csomagon keresztül használja, ez keretrendszerfüggő alkalmazásokban működik. A NuGet feloldja a natív objektumokat, és tartalmazza őket a deps.json
fájlban és a könyvtár alatti runtimes
alkalmazás kimeneti könyvtárában. A .NET onnan tölti be.
A keretrendszerfüggő (nem önálló) alkalmazások esetében, ahol az ICU helyi buildből van felhasználva, további lépéseket kell tennie. A .NET SDK-nak még nincs funkciója a "laza" natív bináris fájlok beépítéséhez deps.json
(lásd ezt az SDK-problémát). Ezt úgy engedélyezheti, hogy további információkat ad hozzá az alkalmazás projektfájljába. Példa:
<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>
Ezt a támogatott futtatókörnyezetek összes ICU bináris fájljához el kell végezni. Emellett az NuGetPackageId
RuntimeTargetsCopyLocalItems
elemcsoport metaadatainak meg kell egyeznie a projekt által ténylegesen hivatkozott NuGet-csomagokkal.
macOS-viselkedés
A macOS eltérő viselkedéssel rendelkezik a függő dinamikus kódtárak feloldásához a fájlban Mach-O
megadott terhelési parancsoktól, mint a Linux-betöltő. A Linux-betöltőben a .NET megpróbálhatja libicudata
libicuuc
és libicui18n
(ebben a sorrendben) kielégíteni az ICU függőségi gráfját. MacOS rendszeren azonban ez nem működik. Az ICU macOS rendszeren történő létrehozásakor alapértelmezés szerint egy dinamikus kódtárat fog lekérni a következő terhelési parancsokkal: libicuuc
. Az alábbi kódrészlet egy példát mutat be.
~/ % 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)
Ezek a parancsok csak hivatkoznak a függő kódtárak nevére az ICU többi összetevőjére vonatkozóan. A betöltő a konvenciók alapján végzi el a dlopen
keresést, amely magában foglalja, hogy ezek a kódtárak a rendszerkönyvtárakban vannak, vagy az LD_LIBRARY_PATH
env vars beállítása, vagy az ICU az alkalmazásszintű címtárban. Ha nem tudja beállítani LD_LIBRARY_PATH
vagy biztosítani, hogy az ICU bináris fájljai az alkalmazásszintű címtárban legyenek, további munkát kell végeznie.
Vannak olyan irányelvek a betöltőhöz, mint például @loader_path
a betöltő, amely arra utasítja a betöltőt, hogy keresse meg a függőséget ugyanabban a könyvtárban, mint a bináris és a terhelési parancs. Ennek kétféle módja van:
install_name_tool -change
Futtassa az alábbi parancsot:
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
Az ICU javítása a telepítési nevek létrehozásához a következővel:
@loader_path
Az autoconf (
./runConfigureICU
) futtatása előtt módosítsa a következő sorokat a következőre: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 a WebAssemblyen
Az ICU egy verziója érhető el, amely kifejezetten a WebAssembly számítási feladataihoz készült. Ez a verzió globális kompatibilitást biztosít az asztali profilokkal. Az ICU-adatfájl méretének 24 MB-ról 1,4 MB-ra (vagy a Brotlival való tömörítés esetén ~0,3 MB-ra) való csökkentéséhez ez a számítási feladat néhány korlátozással rendelkezik.
A következő API-k nem támogatottak:
- CultureInfo.EnglishName
- CultureInfo.NativeName
- DateTimeFormatInfo.NativeCalendarName
- RegionInfo.NativeName
A következő API-k támogatottak korlátozásokkal:
- String.Normalize(NormalizationForm) és String.IsNormalized(NormalizationForm) nem támogatja a ritkán használt FormKC és FormKD űrlapokat.
- RegionInfo.CurrencyNativeName ugyanazt az értéket adja vissza, mint a RegionInfo.CurrencyEnglishName.
Emellett kevesebb területi beállítás is támogatott. A támogatott lista a dotnet/icu adattárban található.