.NET genelleştirme ve ICU

.NET 5'in öncesinde .NET genelleştirme API'leri farklı platformlarda farklı temel kitaplıklar kullanıyordu. Unix'te, API'ler Unicode için Uluslararası Bileşenler (ICU) ve Windows'da Ulusal Dil Desteği (NLS) kullandı. Bu, uygulamaları farklı platformlarda çalıştırırken birkaç genelleştirme API'sinde bazı davranış farklılıklarına neden oldu. Davranış farklılıkları şu alanlarda belirgindi:

  • Kültürler ve kültür verileri
  • Dize büyük/küçük harf
  • Dize sıralama ve arama
  • Sıralama tuşları
  • Dize normalleştirme
  • Uluslararası Etki Alanı Adları (IDN) desteği
  • Linux'ta saat dilimi görünen adı

.NET 5'den başlayarak, geliştiriciler hangi temel kitaplığın kullanıldığı üzerinde daha fazla denetime sahip olur ve uygulamaların platformlar arasındaki farkları önlemesine olanak tanır.

Not

ICU kitaplığının davranışını yönlendiren kültür verileri genellikle çalışma zamanı tarafından değil Common Locale Data Repository (CLDR) tarafından korunur.

Windows'ta ICU

Windows artık genelleştirme görevleri için otomatik olarak kullanılan özelliklerinin bir parçası olarak önceden yüklenmiş bir icu.dll sürümü içerir. Bu değişiklik, .NET'in genelleştirme desteği için bu ICU kitaplığını kullanmasına olanak tanır. Eski Windows sürümleri, .NET 5 ve sonraki sürümlerde olduğu gibi, ICU kitaplığının kullanılamadığı veya yüklenebildiği durumlarda NLS tabanlı uygulamayı kullanmaya geri döner.

Aşağıdaki tabloda, hangi .NET sürümlerinin farklı Windows istemci ve sunucu sürümleri arasında ICU kitaplığını yükleyebilecekleri gösterilmektedir:

.NET sürümü Windows sürümü
.NET 5 veya .NET 6 Windows istemci 10 sürüm 1903 veya üzeri
.NET 5 veya .NET 6 Windows Server 2022 veya üzeri
.NET 7 veya üzeri Windows istemci 10 sürüm 1703 veya üzeri
.NET 7 veya üzeri Windows Server 2019 veya üzeri

Not

.NET 7 ve sonraki sürümleri, .NET 6 ve .NET 5'in aksine eski Windows sürümlerine ICU yükleme özelliğine sahiptir.

Not

ICU kullanırken bile, CurrentCulture, CurrentUICultureve CurrentRegion üyeleri kullanıcı ayarlarını yerine getirmek için Windows işletim sistemi API'lerini kullanmaya devam etmektedir.

Davranış farklılıkları

Uygulamanızı .NET 5 veya sonraki bir sürümü hedefleye yükseltirseniz, genelleştirme olanaklarını kullandığınızı fark etmeseniz bile uygulamanızda değişiklikler görebilirsiniz. Bu bölümde, görebileceğiniz davranış değişikliklerinden biri listelenir, ancak başkaları da vardır.

Indexof

Dizedeki null karakterin \0 dizinini bulmak için çağıran String.IndexOf(String) aşağıdaki kodu göz önünde bulundurun.

const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.CurrentCulture)}");
Console.WriteLine($"{greeting.IndexOf("\0", StringComparison.Ordinal)}");

Varsayılan olarak, String.IndexOf(String) kültüre duyarlı bir dil araması gerçekleştirir. ICU, null karakteri sıfır ağırlıklı bir karakter \0olarak kabul eder ve bu nedenle .NET 5 ve sonraki sürümlerde dil araması kullanılırken karakter dizede bulunmaz. Ancak, NLS null karakteri sıfır ağırlıklı bir karakter \0 olarak değerlendirmez ve .NET Core 3.1 ve önceki sürümlerde dilsel bir arama 3 konumundaki karakteri bulur. Sıralı arama, tüm .NET sürümlerinde 3 konumundaki karakteri bulur.

Kod çözümleme kurallarını CA1307 çalıştırabilirsiniz: Netlik için StringComparison belirtin ve CA1309: Kodunuzda dize karşılaştırmasının belirtilmediğinde veya sıralı olmadığında çağrı sitelerini bulmak için sıralı StringComparison kullanın.

Daha fazla bilgi için bkz . .NET 5+ üzerinde dizeleri karşılaştırırken davranış değişiklikleri.

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

Önemli

Windows'da ICU tablosunda listelenen Windows sürümlerinde çalışan .NET 5+ sürümünde, yukarıdaki kod parçacığı yazdırılır:

True
True
True
False
False

Bu davranışı önlemek için veya parametresi aşırı yüklemesini charStringComparison.Oridinalkullanın.

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

Önemli

Windows'da ICU tablosunda listelenen Windows sürümlerinde çalışan .NET 5+ sürümünde, yukarıdaki kod parçacığı yazdırılır:

True
True
True
False
False

Bu davranışı önlemek için veya parametresi aşırı yüklemesini charStringComparison.Oridinalkullanın.

TimeZoneInfo.FindSystemTimeZoneById

ICU, uygulama Windows üzerinde çalışırken bile IANA saat dilimi kimliklerini kullanarak örnek oluşturma TimeZoneInfo esnekliği sağlar. Benzer şekilde, Windows dışı platformlarda çalışırken bile Windows saat dilimi kimlikleriyle örnekler oluşturabilirsiniz TimeZoneInfo . Ancak, NLS modu veya genelleştirme sabit modu kullanılırken bu işlevselliğin kullanılamadığını unutmayın.

ICU bağımlı API'leri

.NET, ICU'ya bağımlı API'leri kullanıma sunar. Bu API'ler yalnızca ICU kullanılırken başarılı olabilir. Burada bazı örnekler verilmiştir:

Windows'da ICU bölüm tablosunda listelenen Windows sürümlerinde, bahsedilen API'ler tutarlı bir şekilde başarılı olur. Ancak, Windows'un eski sürümlerinde bu API'ler sürekli olarak başarısız olur. Bu gibi durumlarda, bu API'lerin başarısını sağlamak için uygulama yerel ICU özelliğini etkinleştirebilirsiniz. Windows olmayan platformlarda, bu API'ler sürümden bağımsız olarak her zaman başarılı olur.

Ayrıca, uygulamaların bu API'lerin başarısını garanti etmek için genelleştirme sabit modunda veya NLS modunda çalışmadığından emin olmaları çok önemlidir.

ICU yerine NLS kullanma

NLS yerine ICU kullanılması, genelleştirmeyle ilgili bazı işlemlerle davranış farklılıklarına neden olabilir. Bir geliştirici NLS kullanmaya geri dönmek için ICU uygulamasını geri çevirebilir. Uygulamalar NLS modunu aşağıdaki yollardan herhangi biriyle etkinleştirebilir:

  • Proje dosyasında:

    <ItemGroup>
      <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" />
    </ItemGroup>
    
  • runtimeconfig.json dosyasında:

    {
      "runtimeOptions": {
         "configProperties": {
           "System.Globalization.UseNls": true
          }
      }
    }
    
  • Ortam değişkenini DOTNET_SYSTEM_GLOBALIZATION_USENLS veya 1değerine true ayarlayarak.

Not

Projede veya dosyada runtimeconfig.json ayarlanan bir değer ortam değişkenine göre önceliklidir.

Daha fazla bilgi için bkz . Çalışma zamanı yapılandırma ayarları.

Uygulamanızın ICU kullanıp kullanmadığını belirleme

Aşağıdaki kod parçacığı, uygulamanızın NLS değil, ICU kitaplıklarıyla çalışıp çalışmadığını belirlemenize yardımcı olabilir.

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

.NET sürümünü belirlemek için kullanın RuntimeInformation.FrameworkDescription.

Uygulama yerel ICU

ICU'nun her sürümü hata düzeltmeleri getirebilir ve dünyanın dillerini açıklayan Common Locale Data Repository (CLDR) verilerini güncelleştirebilir. ICU sürümleri arasında geçiş yapmak, genelleştirmeyle ilgili işlemler söz konusu olduğunda uygulama davranışını olumsuz etkileyebilir. .NET 5 ve üzeri sürümler, uygulama geliştiricilerinin tüm dağıtımlarda tutarlılık sağlamasına yardımcı olmak için hem Windows hem de Unix'te uygulamaların kendi ICU kopyalarını taşımasına ve kullanmasına olanak tanır.

Uygulamalar, aşağıdaki yollardan biriyle uygulama yerel ICU uygulama modunu kabul edebilir:

  • Proje dosyasında uygun RuntimeHostConfigurationOption değeri ayarlayın:

    <ItemGroup>
      <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<suffix>:<version> or <version>" />
    </ItemGroup>
    
  • veya runtimeconfig.json dosyasında uygun runtimeOptions.configProperties değeri ayarlayın:

    {
      "runtimeOptions": {
         "configProperties": {
           "System.Globalization.AppLocalIcu": "<suffix>:<version> or <version>"
         }
      }
    }
    
  • Ya da ortam değişkenini DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU veya <version>değerine <suffix>:<version> ayarlayarak.

    <suffix>: Genel ICU paketleme kurallarına göre 36 karakterden daha az uzunlukta isteğe bağlı sonek. Özel bir ICU oluştururken, bunu özelleştirerek lib adlarını ve dışarı aktarılan simge adlarını sonek (örneğin, libicuucmyapp) içerecek şekilde oluşturabilirsiniz; burada myapp sonektir.

    <version>: Geçerli bir ICU sürümü, örneğin, 67.1. Bu sürüm ikili dosyaları yüklemek ve dışarı aktarılan simgeleri almak için kullanılır.

Bu seçeneklerden biri ayarlandığında, projenize yapılandırılana version karşılık gelen ve tek gereken bu olan bir Microsoft.ICU.ICU4C.RuntimePackageReference ekleyebilirsiniz.

Alternatif olarak, uygulama yerel anahtarı ayarlandığında ICU yüklemek için .NET, birden çok yolu yoklayan yöntemini kullanır NativeLibrary.TryLoad . yöntemi ilk olarak, uygulamanın dosyasını temel alan dotnet konağı tarafından oluşturulan özelliğindeki deps.json kitaplığı NATIVE_DLL_SEARCH_DIRECTORIES bulmaya çalışır. Daha fazla bilgi için bkz . Varsayılan yoklama.

Bağımsız uygulamalar için, kullanıcı tarafından ICU'nın uygulama dizininde olduğundan emin olmak dışında özel bir eylem gerekmez (bağımsız uygulamalar için, çalışma dizini varsayılan olarak NATIVE_DLL_SEARCH_DIRECTORIESolarak olur).

NuGet paketi aracılığıyla ICU kullanıyorsanız bu, çerçeveye bağımlı uygulamalarda çalışır. NuGet yerel varlıkları çözümler ve bunları dosyaya deps.json ve dizinin altındaki uygulamanın çıkış dizinine runtimes ekler. .NET buradan yükler.

Yerel bir derlemeden ICU kullanılan çerçeveye bağımlı uygulamalar (bağımsız olmayan) için ek adımlar atmalısınız. .NET SDK'sının henüz içine eklenecek deps.json "gevşek" yerel ikili dosyalar için bir özelliği yoktur (bu SDK sorununa bakın). Bunun yerine, uygulamanın proje dosyasına ek bilgiler ekleyerek bunu etkinleştirebilirsiniz. Örneğin:

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

Bu, desteklenen çalışma zamanları için tüm ICU ikili dosyaları için yapılmalıdır. Ayrıca, NuGetPackageId öğe grubundaki RuntimeTargetsCopyLocalItems meta verilerin projenin gerçekten başvurduğunu bir NuGet paketiyle eşleşmesi gerekir.

macOS davranışı

macOS, dosyada Mach-O belirtilen yük komutlarından bağımlı dinamik kitaplıkları çözümlemek için Linux yükleyicisinden farklı bir davranışa sahiptir. Linux yükleyicisinde .NET, ICU bağımlılık grafiğini karşılamak için , libicuucve libicui18n (bu sırayla) deneyebilirlibicudata. Ancak macOS'ta bu işe yaramaz. macOS üzerinde ICU oluştururken, varsayılan olarak içinde bu yük komutlarını libicuuciçeren bir dinamik kitaplık alırsınız. Aşağıdaki kod parçacığı bir örnek gösterir.

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

Bu komutlar yalnızca ICU'nun diğer bileşenleri için bağımlı kitaplıkların adına başvurur. Yükleyici, bu kitaplıkların sistem dizinlerinde bulunmasını veya env değişkenlerini ayarlamayı LD_LIBRARY_PATH ya da uygulama düzeyinde dizinde ICU'nun bulunmasını içeren kuralları izleyerek dlopen aramayı gerçekleştirir. ICU ikili dosyalarının uygulama düzeyi dizininde olduğundan emin olamıyor veya ayarlayamıyorsanız LD_LIBRARY_PATH , bazı ek işler yapmanız gerekir.

Yükleyici için, yükleyiciye bu yük komutuyla ikili ile aynı dizinde bu bağımlılığı aramasını belirten gibi @loader_pathbazı yönergeler vardır. Bunu başarmanın iki yolu vardır:

  • install_name_tool -change

    Aşağıdaki komutları çalıştırın:

    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
    
  • Yükleme adlarını oluşturmak için ICU'ya düzeltme eki uygulama @loader_path

    Otomatik mutabakatı ()./runConfigureICU çalıştırmadan önce şu satırları şu şekilde değiştirin:

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

WebAssembly'de ICU

Özellikle WebAssembly iş yükleri için bir ICU sürümü kullanılabilir. Bu sürüm, masaüstü profilleriyle genelleştirme uyumluluğu sağlar. ICU veri dosyası boyutunu 24 MB'tan 1,4 MB'a (veya Brotli ile sıkıştırıldıysa yaklaşık 0,3 MB) azaltmak için bu iş yükünün birkaç sınırlaması vardır.

Aşağıdaki API'ler desteklenmez:

Aşağıdaki API'ler sınırlamalarla desteklenir:

Ayrıca, daha az yerel ayar desteklenir. Desteklenen liste dotnet/icu deposunda bulunabilir.