Globalisasi .NET dan ICU
Sebelum .NET 5, API globalisasi .NET menggunakan pustaka yang mendasari yang berbeda pada platform yang berbeda. Di Unix, API menggunakan Komponen Internasional untuk Unicode (ICU), dan di Windows, mereka menggunakan Dukungan Bahasa Nasional (NLS). Hal ini mengakibatkan beberapa perbedaan perilaku dalam beberapa API globalisasi saat menjalankan aplikasi di platform yang berbeda. Perbedaan perilaku terlihat di area ini:
- Data budaya dan budaya
- Casing string
- Pengurutan dan pencarian string
- Mengurutkan kunci
- Normalisasi string
- Dukungan Nama Domain Internasional (IDN)
- Nama tampilan zona waktu di Linux
Dimulai dengan .NET 5, pengembang memiliki kontrol lebih besar atas pustaka yang mendasarinya digunakan, memungkinkan aplikasi untuk menghindari perbedaan di seluruh platform.
Catatan
Data budaya yang mendorong perilaku pustaka ICU biasanya dikelola oleh Common Locale Data Repository (CLDR), bukan runtime.
ICU di Windows
Windows sekarang menggabungkan versi icu.dll yang telah diinstal sebelumnya sebagai bagian dari fitur-fiturnya yang secara otomatis digunakan untuk tugas globalisasi. Modifikasi ini memungkinkan .NET menggunakan pustaka ICU ini untuk dukungan globalisasinya. Dalam kasus di mana pustaka ICU tidak tersedia atau tidak dapat dimuat, seperti halnya dengan versi Windows yang lebih lama, .NET 5 dan versi berikutnya kembali menggunakan implementasi berbasis NLS.
Tabel berikut menunjukkan versi .NET mana yang mampu memuat pustaka ICU di berbagai klien Windows dan versi server:
Versi .NET | Versi Windows |
---|---|
.NET 5 atau .NET 6 | Klien Windows 10 versi 1903 atau yang lebih baru |
.NET 5 atau .NET 6 | Windows Server 2022 atau yang lebih baru |
.NET 7 atau yang lebih baru | Klien Windows 10 versi 1703 atau yang lebih baru |
.NET 7 atau yang lebih baru | Windows Server 2019 atau yang lebih baru |
Catatan
.NET 7 dan versi yang lebih baru memiliki kemampuan untuk memuat ICU pada versi Windows yang lebih lama, berbeda dengan .NET 6 dan .NET 5.
Catatan
Bahkan saat menggunakan ICU, CurrentCulture
anggota , , CurrentUICulture
dan CurrentRegion
masih menggunakan API sistem operasi Windows untuk mematuhi pengaturan pengguna.
Perbedaan perilaku
Jika Anda meningkatkan aplikasi untuk menargetkan .NET 5 atau yang lebih baru, Anda mungkin melihat perubahan di aplikasi meskipun Anda tidak menyadari bahwa Anda menggunakan fasilitas globalisasi. Bagian berikut mencantumkan beberapa perubahan perilaku yang mungkin Anda alami.
Pengurutan string dan System.Globalization.CompareOptions
CompareOptions
adalah enumerasi opsi yang dapat diteruskan untuk String.Compare
memengaruhi bagaimana dua string dibandingkan.
Membandingkan string untuk kesetaraan dan menentukan urutan sortirnya berbeda antara NLS dan ICU. Secara khusus:
- Urutan pengurutan string default berbeda, jadi ini akan terlihat meskipun Anda tidak menggunakan
CompareOptions
secara langsung. Saat menggunakan ICU,None
opsi default berkinerja sama denganStringSort
.StringSort
mengurutkan karakter non-alfanumerik sebelum karakter alfanumerik (jadi "tagihan" mengurutkan sebelum "tagihan", misalnya). Untuk memulihkan fungsionalitas sebelumnyaNone
, Anda harus menggunakan implementasi berbasis NLS. - Penanganan default karakter ligatur berbeda. Di bawah NLS, ligatur dan rekan non-ligatur mereka (misalnya, "oeuf" dan "œuf") dianggap sama, tetapi ini tidak terjadi dengan ICU di .NET. Hal ini karena kekuatan kolatasi yang berbeda antara kedua implementasi. Untuk memulihkan perilaku NLS saat menggunakan ICU, gunakan nilai .
CompareOptions.IgnoreNonSpace
String.IndexOf
Pertimbangkan kode berikut yang memanggil String.IndexOf(String) untuk menemukan indeks karakter \0
null dalam 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)}");
- Di .NET Core 3.1 dan versi yang lebih lama di Windows, cuplikan mencetak
3
pada masing-masing dari tiga baris. - Untuk .NET 5 dan versi yang lebih baru yang berjalan pada versi Windows yang tercantum dalam ICU pada tabel bagian Windows , cuplikan mencetak
0
,0
, dan3
(untuk pencarian ordinal).
Secara default, String.IndexOf(String) melakukan pencarian linguistik sadar budaya. ICU menganggap karakter \0
null sebagai karakter dengan bobot nol, dan dengan demikian karakter tidak ditemukan dalam string saat menggunakan pencarian linguistik pada .NET 5 dan yang lebih baru. Namun, NLS tidak menganggap karakter \0
null sebagai karakter nol berat, dan pencarian linguistik pada .NET Core 3.1 dan yang lebih lama menemukan karakter pada posisi 3. Pencarian ordinal menemukan karakter pada posisi 3 pada semua versi .NET.
Anda dapat menjalankan aturan analisis kode CA1307: Tentukan StringComparison untuk kejelasan dan CA1309: Gunakan Perbandingan String ordinal untuk menemukan situs panggilan dalam kode Anda di mana perbandingan string tidak ditentukan atau tidak ordinal.
Untuk informasi selengkapnya, lihat Perubahan perilaku saat membandingkan string pada .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'));
Penting
Di .NET 5+ yang berjalan pada versi Windows yang tercantum dalam ICU pada tabel Windows , cuplikan sebelumnya mencetak:
True
True
True
False
False
Untuk menghindari perilaku ini, gunakan char
parameter kelebihan beban atau 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'));
Penting
Di .NET 5+ yang berjalan pada versi Windows yang tercantum dalam ICU pada tabel Windows , cuplikan sebelumnya mencetak:
True
True
True
False
False
Untuk menghindari perilaku ini, gunakan char
parameter kelebihan beban atau StringComparison.Ordinal
.
TimeZoneInfo.FindSystemTimeZoneById
ICU memberikan fleksibilitas untuk membuat TimeZoneInfo instans menggunakan ID zona waktu IANA , bahkan ketika aplikasi berjalan di Windows. Demikian pula, Anda dapat membuat TimeZoneInfo instans dengan ID zona waktu Windows, bahkan saat berjalan di platform non-Windows. Namun, penting untuk dicatat bahwa fungsionalitas ini tidak tersedia saat menggunakan mode NLS atau mode invarian globalisasi.
Singkatan hari dalam seminggu
Metode ini DateTimeFormatInfo.GetShortestDayName(DayOfWeek) memperoleh nama hari yang disingkat terpendek untuk hari tertentu dalam seminggu.
- Dalam .NET Core 3.1 dan versi yang lebih lama di Windows, singkatan hari dalam seminggu ini terdiri dari dua karakter, misalnya, "Su".
- Dalam .NET 5 dan versi yang lebih baru, singkatan hari dalam seminggu ini hanya terdiri dari satu karakter, misalnya, "S".
API yang bergantung pada ICU
.NET memperkenalkan API yang bergantung pada ICU. API ini hanya dapat berhasil saat menggunakan ICU. Berikut adalah beberapa contoh:
Pada versi Windows yang tercantum dalam ICU pada tabel bagian Windows , API yang disebutkan berhasil. Namun, pada versi Windows yang lebih lama, API ini gagal. Dalam kasus seperti itu , Anda dapat mengaktifkan fitur ICU lokal aplikasi untuk memastikan keberhasilan API ini. Pada platform non-Windows, API ini selalu berhasil terlepas dari versinya.
Selain itu, sangat penting bagi aplikasi untuk memastikan bahwa aplikasi tidak berjalan dalam mode invarian globalisasi atau mode NLS untuk menjamin keberhasilan API ini.
Menggunakan NLS alih-alih ICU
Menggunakan ICU alih-alih NLS dapat mengakibatkan perbedaan perilaku dengan beberapa operasi terkait globalisasi. Untuk kembali menggunakan NLS, Anda dapat menolak implementasi ICU. Aplikasi dapat mengaktifkan mode NLS dengan salah satu cara berikut:
Dalam file proyek:
<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" /> </ItemGroup>
Dalam file
runtimeconfig.json
:{ "runtimeOptions": { "configProperties": { "System.Globalization.UseNls": true } } }
Dengan mengatur variabel
DOTNET_SYSTEM_GLOBALIZATION_USENLS
lingkungan ke nilaitrue
atau1
.
Catatan
Nilai yang ditetapkan dalam proyek atau dalam runtimeconfig.json
file lebih diutamakan daripada variabel lingkungan.
Untuk informasi selengkapnya, lihat Pengaturan konfigurasi runtime.
Menentukan apakah aplikasi Anda menggunakan ICU
Cuplikan kode berikut dapat membantu Anda menentukan apakah aplikasi Anda berjalan dengan pustaka ICU (dan bukan 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;
}
Untuk menentukan versi .NET, gunakan RuntimeInformation.FrameworkDescription.
ICU lokal aplikasi
Setiap rilis ICU mungkin membawa perbaikan bug dan memperbarui data Common Locale Data Repository (CLDR) yang menjelaskan bahasa dunia. Berpindah antar versi ICU dapat secara halus memengaruhi perilaku aplikasi dalam hal operasi terkait globalisasi. Untuk membantu pengembang aplikasi memastikan konsistensi di semua penyebaran, .NET 5 dan versi yang lebih baru memungkinkan aplikasi di Windows dan Unix untuk membawa dan menggunakan salinan ICU mereka sendiri.
Aplikasi dapat memilih mode implementasi ICU lokal aplikasi dengan salah satu cara berikut:
Dalam file proyek, atur nilai yang sesuai
RuntimeHostConfigurationOption
:<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<suffix>:<version> or <version>" /> </ItemGroup>
Atau dalam file runtimeconfig.json , atur nilai yang sesuai
runtimeOptions.configProperties
:{ "runtimeOptions": { "configProperties": { "System.Globalization.AppLocalIcu": "<suffix>:<version> or <version>" } } }
Atau dengan mengatur variabel
DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU
lingkungan ke nilai<suffix>:<version>
atau<version>
.<suffix>
: Panjang akhiran opsional kurang dari 36 karakter, mengikuti konvensi pengemasan ICU publik. Saat membangun ICU kustom, Anda dapat menyesuaikannya untuk menghasilkan nama lib dan nama simbol yang diekspor untuk berisi akhiran, misalnya,libicuucmyapp
, di manamyapp
akhirannya.<version>
: Versi ICU yang valid, misalnya, 67.1. Versi ini digunakan untuk memuat biner dan untuk mendapatkan simbol yang diekspor.
Ketika salah satu opsi ini diatur, Anda dapat menambahkan Microsoft.ICU.ICU4C.Runtime PackageReference
ke proyek Anda yang sesuai dengan yang dikonfigurasi version
dan itu saja yang diperlukan.
Atau, untuk memuat ICU saat sakelar app-local diatur, .NET menggunakan NativeLibrary.TryLoad metode , yang memeriksa beberapa jalur. Metode ini pertama-tama mencoba menemukan pustaka di NATIVE_DLL_SEARCH_DIRECTORIES
properti , yang dibuat oleh host dotnet berdasarkan deps.json
file untuk aplikasi. Untuk informasi selengkapnya, lihat Pemeriksaan default.
Untuk aplikasi mandiri, tidak ada tindakan khusus yang diperlukan oleh pengguna, selain memastikan ICU berada di direktori aplikasi (untuk aplikasi mandiri, direktori kerja default ke NATIVE_DLL_SEARCH_DIRECTORIES
).
Jika Anda menggunakan ICU melalui paket NuGet, ini berfungsi dalam aplikasi yang bergantung pada kerangka kerja. NuGet menyelesaikan aset asli dan menyertakannya dalam deps.json
file dan di direktori output untuk aplikasi di bawah runtimes
direktori. .NET memuatnya dari sana.
Untuk aplikasi yang bergantung pada kerangka kerja (tidak mandiri) di mana ICU digunakan dari build lokal, Anda harus mengambil langkah tambahan. .NET SDK belum memiliki fitur untuk biner asli "longgar" yang akan dimasukkan ke dalam deps.json
(lihat masalah SDK ini). Sebagai gantinya, Anda dapat mengaktifkan ini dengan menambahkan informasi tambahan ke dalam file proyek aplikasi. Contohnya:
<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>
Ini harus dilakukan untuk semua biner ICU untuk runtime yang didukung. Selain itu NuGetPackageId
, metadata dalam RuntimeTargetsCopyLocalItems
grup item perlu cocok dengan paket NuGet yang sebenarnya dirujuk oleh proyek.
Perilaku macOS
macOS memiliki perilaku yang berbeda untuk menyelesaikan pustaka dinamis dependen dari perintah beban yang ditentukan dalam Mach-O
file daripada pemuat Linux. Di loader Linux, .NET dapat mencoba libicudata
, , libicuuc
dan libicui18n
(dalam urutan tersebut) untuk memenuhi grafik dependensi ICU. Namun, di macOS, ini tidak berfungsi. Saat membangun ICU di macOS, Anda, secara default, mendapatkan pustaka dinamis dengan perintah beban ini di libicuuc
. Cuplikan berikut menunjukkan contoh.
~/ % 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)
Perintah ini hanya mereferensikan nama pustaka dependen untuk komponen ICU lainnya. Loader melakukan pencarian setelah dlopen
konvensi, yang melibatkan memiliki pustaka ini di direktori sistem atau mengatur LD_LIBRARY_PATH
env vars, atau memiliki ICU di direktori tingkat aplikasi. Jika Anda tidak dapat mengatur LD_LIBRARY_PATH
atau memastikan bahwa biner ICU berada di direktori tingkat aplikasi, Anda harus melakukan beberapa pekerjaan tambahan.
Ada beberapa arahan untuk loader, seperti @loader_path
, yang memberi tahu loader untuk mencari dependensi tersebut di direktori yang sama dengan biner dengan perintah beban tersebut. Ada dua cara untuk mencapai hal ini:
install_name_tool -change
Jalankan perintah berikut:
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 untuk menghasilkan nama penginstalan dengan
@loader_path
Sebelum menjalankan autoconf (
./runConfigureICU
), ubah baris ini menjadi: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 di WebAssembly
Versi ICU tersedia yang khusus untuk beban kerja WebAssembly. Versi ini menyediakan kompatibilitas globalisasi dengan profil desktop. Untuk mengurangi ukuran file data ICU dari 24 MB menjadi 1,4 MB (atau ~0,3 MB jika dikompresi dengan Brotli), beban kerja ini memiliki beberapa batasan.
API berikut ini tidak didukung:
- CultureInfo.EnglishName
- CultureInfo.NativeName
- DateTimeFormatInfo.NativeCalendarName
- RegionInfo.NativeName
API berikut didukung dengan batasan:
- String.Normalize(NormalizationForm)dan String.IsNormalized(NormalizationForm) tidak mendukung formulir dan FormKD yang jarang digunakanFormKC.
- RegionInfo.CurrencyNativeName mengembalikan nilai yang sama dengan RegionInfo.CurrencyEnglishName.
Selain itu, lebih sedikit lokal yang didukung. Daftar yang didukung dapat ditemukan di repositori dotnet/icu.