.NET globalization and ICU

Vóór .NET 5 gebruikten de .NET globalization-API's verschillende onderliggende bibliotheken op verschillende platforms. Op Unix gebruikten de API's International Components for Unicode (ICU) en in Windows gebruikten ze National Language Support (NLS). Dit heeft geleid tot enkele gedragsverschillen in een handvol globalisatie-API's bij het uitvoeren van toepassingen op verschillende platforms. Gedragsverschillen zijn op deze gebieden duidelijk:

  • Culturen en cultuurgegevens
  • Tekenreeksbehuizing
  • Tekenreeks sorteren en zoeken
  • Sleutels sorteren
  • Tekenreeksnormalisatie
  • Ondersteuning voor geinternationaliseerde domeinnamen (IDN)
  • Weergavenaam van tijdzone in Linux

Vanaf .NET 5 hebben ontwikkelaars meer controle over welke onderliggende bibliotheek wordt gebruikt, zodat toepassingen verschillen tussen platforms kunnen voorkomen.

Notitie

De cultuurgegevens die het gedrag van de ICU-bibliotheek aansturen, worden meestal onderhouden door de Common Locale Data Repository (CLDR) en niet door de runtime.

ICU in Windows

Windows bevat nu een vooraf geïnstalleerde icu.dll versie als onderdeel van de functies die automatisch worden gebruikt voor globalisatietaken. Met deze wijziging kan .NET gebruikmaken van deze ICU-bibliotheek voor de ondersteuning van globalisatie. In gevallen waarin de ICU-bibliotheek niet beschikbaar is of niet kan worden geladen, zoals het geval is bij oudere Windows-versies, .NET 5 en volgende versies terugkeren naar het gebruik van de implementatie op basis van NLS.

In de volgende tabel ziet u welke versies van .NET de ICU-bibliotheek kunnen laden in verschillende Windows-client- en serverversies:

.NET-versie Windows-versie
.NET 5 of .NET 6 Windows-client 10 versie 1903 of hoger
.NET 5 of .NET 6 Windows Server 2022 of hoger
.NET 7 of hoger Windows-client 10 versie 1703 of hoger
.NET 7 of hoger Windows Server 2019 of hoger

Notitie

.NET 7 en latere versies hebben de mogelijkheid om ICU op oudere Windows-versies te laden, in tegenstelling tot .NET 6 en .NET 5.

Notitie

Zelfs bij het gebruik van ICU, de CurrentCulture, CurrentUICultureen CurrentRegion leden gebruiken nog steeds Windows-besturingssysteem-API's om gebruikersinstellingen te respecteren.

Gedragsverschillen

Als u uw app upgradet naar .NET 5 of hoger, ziet u mogelijk wijzigingen in uw app, zelfs als u zich niet realiseert dat u gebruikmaakt van globalisatiefaciliteiten. Deze sectie bevat een van de gedragswijzigingen die u mogelijk ziet, maar er zijn ook andere.

String.IndexOf

Houd rekening met de volgende code die de index van het null-teken \0 in een tekenreeks aanroeptString.IndexOf(String).

const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.CurrentCulture)}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.Ordinal)}");
  • In .NET Core 3.1 en eerdere versies in Windows wordt het fragment op elk van de drie regels afgedrukt 3 .
  • Voor .NET 5- en latere versies die worden uitgevoerd op de Windows-versies die worden vermeld in de sectietabel ICU in Windows, worden het fragment afgedrukt 0en 03 (voor de rangschikkingszoekopdracht).

Voert standaard String.IndexOf(String) een cultuurbewuste taalkundige zoekopdracht uit. ICU beschouwt het null-teken \0 als een nulgewichtteken en het teken wordt dus niet gevonden in de tekenreeks bij het gebruik van een taalkundige zoekopdracht op .NET 5 en hoger. NLS beschouwt het null-teken \0 echter niet als een nulteken en een taalkundige zoekopdracht op .NET Core 3.1 en eerder zoekt het teken op positie 3. Met een rangschikker wordt het teken gevonden op positie 3 op alle .NET-versies.

U kunt codeanalyseregels CA1307 uitvoeren: Geef StringComparison op voor duidelijkheid en CA1309: Gebruik ordinale StringComparison om aanroepsites te vinden in uw code waar de tekenreeksvergelijking niet is opgegeven of niet ordinaal is.

Zie Gedragswijzigingen bij het vergelijken van tekenreeksen op .NET 5+ voor meer informatie.

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'));

Belangrijk

In .NET 5+ uitgevoerd op Windows-versies die worden vermeld in de ICU in Windows-tabel , wordt het voorgaande fragment afgedrukt:

True
True
True
False
False

Gebruik de char parameteroverbelasting of StringComparison.Oridinalom dit gedrag te voorkomen.

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'));

Belangrijk

In .NET 5+ uitgevoerd op Windows-versies die worden vermeld in de ICU in Windows-tabel , wordt het voorgaande fragment afgedrukt:

True
True
True
False
False

Gebruik de char parameteroverbelasting of StringComparison.Oridinalom dit gedrag te voorkomen.

TimeZoneInfo.FindSystemTimeZoneById

ICU biedt de flexibiliteit om exemplaren te maken TimeZoneInfo met behulp van IANA-tijdzone-id's , zelfs wanneer de toepassing wordt uitgevoerd in Windows. Op dezelfde manier kunt u exemplaren maken TimeZoneInfo met Windows-tijdzone-id's, zelfs wanneer deze worden uitgevoerd op niet-Windows-platforms. Het is echter belangrijk om te weten dat deze functionaliteit niet beschikbaar is bij het gebruik van de NLS-modus of globalisatie invariantmodus.

ICU-afhankelijke API's

.NET heeft API's geïntroduceerd die afhankelijk zijn van de ICU. Deze API's kunnen alleen slagen wanneer u ICU gebruikt. Hieronder volgen een aantal voorbeelden:

In de Windows-versies die worden vermeld in de sectietabel ICU in Windows , slagen de vermelde API's consistent. In oudere versies van Windows mislukken deze API's echter consistent. In dergelijke gevallen kunt u de app-lokale ICU-functie inschakelen om ervoor te zorgen dat deze API's worden geslaagd. Op niet-Windows-platforms slagen deze API's altijd, ongeacht de versie.

Bovendien is het van cruciaal belang voor apps om ervoor te zorgen dat ze niet worden uitgevoerd in de invariante modus van globalisatie of NLS-modus om het succes van deze API's te garanderen.

NLS gebruiken in plaats van ICU

Het gebruik van ICU in plaats van NLS kan leiden tot gedragsverschillen met sommige globaliseringsgerelateerde bewerkingen. Om terug te keren naar het gebruik van NLS, kan een ontwikkelaar zich afmelden voor de ICU-implementatie. Toepassingen kunnen de NLS-modus op een van de volgende manieren inschakelen:

  • In het projectbestand:

    <ItemGroup>
      <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" />
    </ItemGroup>
    
  • In het bestand runtimeconfig.json:

    {
      "runtimeOptions": {
         "configProperties": {
           "System.Globalization.UseNls": true
          }
      }
    }
    
  • Door de omgevingsvariabele DOTNET_SYSTEM_GLOBALIZATION_USENLS in te stellen op de waarde true of 1.

Notitie

Een waarde die is ingesteld in het project of in het runtimeconfig.json bestand heeft voorrang op de omgevingsvariabele.

Zie Runtime-configuratie-instellingen voor meer informatie.

Bepalen of uw app ICU gebruikt

Met het volgende codefragment kunt u bepalen of uw app wordt uitgevoerd met ICU-bibliotheken (en niet MET 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;
}

Als u de versie van .NET wilt bepalen, gebruikt u RuntimeInformation.FrameworkDescription.

App-lokale ICU

Elke release van ICU kan oplossingen voor fouten bevatten, evenals bijgewerkte CLDR-gegevens (Common Locale Data Repository) die de talen van de wereld beschrijft. Verplaatsen tussen versies van ICU kan subtly invloed hebben op het gedrag van apps als het gaat om globaliseringsgerelateerde bewerkingen. Om toepassingsontwikkelaars te helpen consistentie in alle implementaties te garanderen, kunnen .NET 5 en latere versies apps op zowel Windows als Unix hun eigen exemplaar van ICU meenemen en gebruiken.

Toepassingen kunnen zich op een van de volgende manieren aanmelden voor een app-lokale ICU-implementatiemodus:

  • Stel in het projectbestand de juiste RuntimeHostConfigurationOption waarde in:

    <ItemGroup>
      <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<suffix>:<version> or <version>" />
    </ItemGroup>
    
  • Of stel in het bestand runtimeconfig.json de juiste runtimeOptions.configProperties waarde in:

    {
      "runtimeOptions": {
         "configProperties": {
           "System.Globalization.AppLocalIcu": "<suffix>:<version> or <version>"
         }
      }
    }
    
  • Of door de omgevingsvariabele DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU in te stellen op de waarde <suffix>:<version> of <version>.

    <suffix>: Optioneel achtervoegsel van minder dan 36 tekens lang, na de openbare ICU-verpakkingsconventies. Wanneer u een aangepaste ICU bouwt, kunt u deze aanpassen om de lib-namen en geëxporteerde symboolnamen te produceren die een achtervoegsel bevatten, bijvoorbeeld, libicuucmyappwaar myapp is het achtervoegsel.

    <version>: Een geldige ICU-versie, bijvoorbeeld 67.1. Deze versie wordt gebruikt om de binaire bestanden te laden en de geëxporteerde symbolen op te halen.

Wanneer een van deze opties is ingesteld, kunt u een Microsoft.ICU.ICU4C.RuntimePackageReference toevoegen aan uw project dat overeenkomt met de geconfigureerde version en dat is alles wat nodig is.

Als u de ICU wilt laden wanneer de app-lokale switch is ingesteld, gebruikt .NET de NativeLibrary.TryLoad methode, die meerdere paden test. De methode probeert eerst de bibliotheek in de NATIVE_DLL_SEARCH_DIRECTORIES eigenschap te vinden, die wordt gemaakt door de dotnet-host op basis van het deps.json bestand voor de app. Zie De standaardprompt voor meer informatie.

Voor zelfstandige apps is er geen speciale actie vereist voor de gebruiker, behalve het controleren of de ICU zich in de app-map bevindt (voor zelfstandige apps is de werkmap standaard NATIVE_DLL_SEARCH_DIRECTORIESingesteld op ).

Als u ICU via een NuGet-pakket gebruikt, werkt dit in frameworkafhankelijke toepassingen. NuGet lost de systeemeigen assets op en neemt deze op in het deps.json bestand en in de uitvoermap voor de toepassing onder de runtimes map. .NET laadt het van daaruit.

Voor frameworkafhankelijke apps (niet zelfstandig) waarbij ICU wordt gebruikt vanuit een lokale build, moet u aanvullende stappen uitvoeren. De .NET SDK heeft nog geen functie voor 'losse' systeemeigen binaire bestanden die moeten worden opgenomen deps.json (zie dit SDK-probleem). In plaats daarvan kunt u dit inschakelen door aanvullende informatie toe te voegen aan het projectbestand van de toepassing. Voorbeeld:

<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>

Dit moet worden gedaan voor alle binaire ICU-bestanden voor de ondersteunde runtimes. NuGetPackageId De metagegevens in de RuntimeTargetsCopyLocalItems itemgroep moeten ook overeenkomen met een NuGet-pakket waarnaar het project daadwerkelijk verwijst.

macOS-gedrag

macOS heeft een ander gedrag voor het oplossen van afhankelijke dynamische bibliotheken dan de laadopdrachten die zijn opgegeven in het Mach-O bestand dan het Linux-laadprogramma. In het Linux-laadprogramma kan .NET proberen libicudata, libicuucen libicui18n (in die volgorde) voldoen aan de ICU-afhankelijkheidsgrafiek. In macOS werkt dit echter niet. Wanneer u ICU bouwt in macOS, krijgt u standaard een dynamische bibliotheek met deze laadopdrachten in libicuuc. In het volgende fragment ziet u een voorbeeld.

~/ % 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)

Deze opdrachten verwijzen alleen naar de naam van de afhankelijke bibliotheken voor de andere onderdelen van de ICU. Het laadprogramma voert de zoekopdracht uit volgens de dlopen conventies, waarbij deze bibliotheken zich in de systeemmappen bevinden of de LD_LIBRARY_PATH env vars instellen, of dat de ICU zich in de map op app-niveau bevindt. Als u niet kunt instellen LD_LIBRARY_PATH of controleren of binaire ICU-bestanden zich in de map op app-niveau bevinden, moet u wat extra werk doen.

Er zijn enkele instructies voor het laadprogramma, zoals @loader_path, waarmee het laadprogramma naar die afhankelijkheid in dezelfde map moet zoeken als het binaire bestand met die load-opdracht. Er zijn twee manieren om dit te bereiken:

  • install_name_tool -change

    Voer de volgende opdrachten uit:

    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
    
  • Patch-ICU voor het produceren van de installatienamen met @loader_path

    Voordat u autoconf (./runConfigureICU) uitvoert, wijzigt u deze regels in:

    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 op WebAssembly

Er is een versie van ICU beschikbaar die specifiek is voor WebAssembly-workloads. Deze versie biedt compatibiliteit met globalisatie met bureaubladprofielen. Als u de grootte van het ICU-gegevensbestand wilt verkleinen van 24 MB tot 1,4 MB (of ~0,3 MB indien gecomprimeerd met Brotli), heeft deze workload een aantal beperkingen.

De volgende API's worden niet ondersteund:

De volgende API's worden ondersteund met beperkingen:

Daarnaast worden er minder landinstellingen ondersteund. De ondersteunde lijst vindt u in de dotnet/icu-opslagplaats.