.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ó 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 CurrentCulturewindowsos CurrentRegionCurrentUICultureoperá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. Ez a szakasz felsorolja azokat a viselkedési módosításokat, amelyeket láthat, de vannak mások is.

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)}");

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.

A CA1307 kódelemzési szabályokat futtathatja: A StringComparison megadása az egyértelműség érdekében és a CA1309: Az ordinal StringComparison használatával olyan híváswebhelyeket kereshet a kódban, ahol a sztringek összehasonlítása 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.Oridinal.

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.

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 következetesen sikeresek lesznek. A Windows régebbi verzióiban azonban ezek az API-k folyamatosan sikertelenek lesznek. Ilyen esetekben engedélyezheti az alkalmazás helyi ICU funkcióját ezeknek az API-knak a sikerességéhez. A nem Windows-platformokon ezek az API-k a verziótól függetlenül mindig sikeresek lesznek.

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, a fejlesztő leiratkozhat az ICU-implementációról. 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ékre true vagy 1é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 hozhat magával, valamint frissített Common Locale Data Repository (CLDR) adatokat, 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 engedélyezhetik 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, libicuucmyapphogy hol myapp 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-tPackageReference 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_DIRECTORIESa 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 NuGetPackageIdRuntimeTargetsCopyLocalItems 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 libicudatalibicuucé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_patha 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:

A következő API-k támogatottak korlátozásokkal:

Emellett kevesebb területi beállítás is támogatott. A támogatott lista a dotnet/icu adattárban található.