.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 deze ICU-bibliotheek gebruiken 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
, CurrentUICulture
en 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. De volgende sectie bevat enkele gedragswijzigingen die u mogelijk ondervindt.
Tekenreeks sorteren en System.Globalization.CompareOptions
CompareOptions
is de opsomming opties die kunnen worden doorgegeven om te String.Compare
beïnvloeden hoe twee tekenreeksen worden vergeleken.
Het vergelijken van tekenreeksen voor gelijkheid en het bepalen van hun sorteervolgorde verschilt tussen NLS en ICU. Met name:
- De standaardvolgorde voor tekenreeksen verschilt, dus dit is duidelijk, zelfs als u niet rechtstreeks gebruikt
CompareOptions
. Wanneer u ICU gebruikt, voert deNone
standaardoptie hetzelfde uit alsStringSort
.StringSort
sorteert niet-alfanumerieke tekens vóór alfanumerieke tekens (dus 'bill's' sorteert vóór 'facturen', bijvoorbeeld). Als u de vorigeNone
functionaliteit wilt herstellen, moet u de implementatie op basis van NLS gebruiken. - De standaardverwerking van ligatuurtekens verschilt. Onder NLS worden ligaturen en hun niet-ligatuur-tegenhangers (bijvoorbeeld 'oeuf' en 'f') als gelijk beschouwd, maar dit is niet het geval bij ICU in .NET. Dit komt door een andere sorteringssterkte tussen de twee implementaties. Gebruik de
CompareOptions.IgnoreNonSpace
waarde om het NLS-gedrag te herstellen wanneer u ICU gebruikt.
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
0
en0
3
(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.Oridinal
om 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.Ordinal
om 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.
Afkortingen van de dag van de week
De DateTimeFormatInfo.GetShortestDayName(DayOfWeek) methode verkrijgt de kortste verkorte dagnaam voor een opgegeven dag van de week.
- In .NET Core 3.1 en eerdere versies in Windows bestaan deze dag-van-week afkortingen uit twee tekens, bijvoorbeeld 'Su'.
- In .NET 5 en latere versies bestaan deze afkortingen van de dag van de week uit slechts één teken, bijvoorbeeld 'S'.
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. In oudere versies van Windows mislukken deze API's echter. 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. Als u wilt terugkeren naar het gebruik van NLS, kunt u 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 waardetrue
of1
.
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 brengt mogelijk bugfixes en bijgewerkte CLDR-gegevens (Common Locale Data Repository) mee 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,libicuucmyapp
waarmyapp
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.Runtime PackageReference
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_DIRECTORIES
ingesteld 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
, libicuuc
en 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:
- CultureInfo.EnglishName
- CultureInfo.NativeName
- DateTimeFormatInfo.NativeCalendarName
- RegionInfo.NativeName
De volgende API's worden ondersteund met beperkingen:
- String.Normalize(NormalizationForm) en String.IsNormalized(NormalizationForm) bieden geen ondersteuning voor de zelden gebruikte FormKC en FormKD formulieren.
- RegionInfo.CurrencyNativeName retourneert dezelfde waarde als RegionInfo.CurrencyEnglishName.
Daarnaast worden er minder landinstellingen ondersteund. De ondersteunde lijst vindt u in de dotnet/icu-opslagplaats.