Bagikan melalui


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, CurrentCultureanggota , , CurrentUICulturedan 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 dengan StringSort. StringSort mengurutkan karakter non-alfanumerik sebelum karakter alfanumerik (jadi "tagihan" mengurutkan sebelum "tagihan", misalnya). Untuk memulihkan fungsionalitas sebelumnya None , 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, dan 3 (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 nilai true atau 1.

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 mana myapp 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, , libicuucdan 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:

API berikut didukung dengan batasan:

Selain itu, lebih sedikit lokal yang didukung. Daftar yang didukung dapat ditemukan di repositori dotnet/icu.