Share via


.NET 5+ üzerinde dizeleri karşılaştırırken davranış değişiklikleri

.NET 5, genelleştirme API'lerinin desteklenen tüm platformlarda varsayılan olarak ICU kullandığı çalışma zamanı davranış değişikliğini tanıtır. Bu, .NET Core'un önceki sürümlerinden ve Windows üzerinde çalışırken işletim sisteminin ulusal dil desteği (NLS) işlevini kullanan .NET Framework'ten bir çıkıştır. Davranış değişikliğini geri döndürebilecek uyumluluk anahtarları da dahil olmak üzere bu değişiklikler hakkında daha fazla bilgi için bkz . .NET genelleştirme ve ICU.

Değişiklik nedeni

Bu değişiklik birleştirmek için kullanıma sunulmuştur. DESTEKLENEN tüm işletim sistemlerinde NET'in genelleştirme davranışı. Ayrıca uygulamaların işletim sisteminin yerleşik kitaplıklarına bağımlı olmak yerine kendi genelleştirme kitaplıklarını paketlemesini sağlar. Daha fazla bilgi için hataya neden olan değişiklik bildirimine bakın.

Davranış farklılıkları

Bir bağımsız değişken alan StringComparison aşırı yüklemeyi çağırmadan gibi string.IndexOf(string) işlevler kullanırsanız sıralı bir arama gerçekleştirmeyi amaçlayabilirsiniz, ancak bunun yerine istemeden kültüre özgü davranışlara bağımlılık alırsınız. NLS ve ICU dil karşılaştırıcılarında farklı mantık uyguladığından, gibi string.IndexOf(string) yöntemlerin sonuçları beklenmeyen değerler döndürebilir.

Bu durum, küreselleştirme tesislerinin her zaman etkin olmasını beklemediğiniz yerlerde bile kendini gösterebilir. Örneğin, aşağıdaki kod geçerli çalışma zamanına bağlı olarak farklı bir yanıt üretebilir.

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

// The snippet prints:
//
// '3' when running on .NET Core 2.x - 3.x (Windows)
// '0' when running on .NET 5 or later (Windows)
// '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
// '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)

string s = "Hello\r\nworld!";
int idx = s.IndexOf("\n");
Console.WriteLine(idx);

// The snippet prints:
//
// '6' when running on .NET Core 3.1
// '-1' when running on .NET 5 or .NET Core 3.1 (non-Windows OS)
// '-1' when running on .NET 5 (Windows 10 May 2019 Update or later)
// '6' when running on .NET 6+ (all Windows and non-Windows OSs)

Daha fazla bilgi için bkz . Genelleştirme API'leri Windows'ta ICU kitaplıklarını kullanır.

Beklenmeyen davranışlara karşı koruma

Bu bölüm ,NET 5'teki beklenmeyen davranış değişiklikleriyle başa çıkmak için iki seçenek sağlar.

Kod çözümleyicilerini etkinleştirme

Kod çözümleyicileri , muhtemelen buggy çağrı sitelerini algılayabilir. Şaşırtıcı davranışlara karşı korunmaya yardımcı olmak için projenizde .NET derleyici platformu (Roslyn) çözümleyicilerini etkinleştirmenizi öneririz. Çözümleyiciler, sıralı bir karşılaştırıcının büyük olasılıkla amaçlandığı durumlarda yanlışlıkla dil karşılaştırıcısı kullanan kodlara bayrak eklemeye yardımcı olur. Aşağıdaki kurallar bu sorunları işaretlemeye yardımcı olmalıdır:

Bu belirli kurallar varsayılan olarak etkinleştirilmez. Bunları etkinleştirmek ve ihlalleri derleme hataları olarak göstermek için proje dosyanızda aşağıdaki özellikleri ayarlayın:

<PropertyGroup>
  <AnalysisMode>All</AnalysisMode>
  <WarningsAsErrors>$(WarningsAsErrors);CA1307;CA1309;CA1310</WarningsAsErrors>
</PropertyGroup>

Aşağıdaki kod parçacığı, ilgili kod çözümleyici uyarılarını veya hatalarını üreten kod örneklerini gösterir.

//
// Potentially incorrect code - answer might vary based on locale.
//
string s = GetString();
// Produces analyzer warning CA1310 for string; CA1307 matches on char ','
int idx = s.IndexOf(",");
Console.WriteLine(idx);

//
// Corrected code - matches the literal substring ",".
//
string s = GetString();
int idx = s.IndexOf(",", StringComparison.Ordinal);
Console.WriteLine(idx);

//
// Corrected code (alternative) - searches for the literal ',' character.
//
string s = GetString();
int idx = s.IndexOf(',');
Console.WriteLine(idx);

Benzer şekilde, sıralanmış bir dize koleksiyonunun örneğini oluştururken veya var olan bir dize tabanlı koleksiyonu sıralarken, açık bir karşılaştırıcı belirtin.

//
// Potentially incorrect code - behavior might vary based on locale.
//
SortedSet<string> mySet = new SortedSet<string>();
List<string> list = GetListOfStrings();
list.Sort();

//
// Corrected code - uses ordinal sorting; doesn't vary by locale.
//
SortedSet<string> mySet = new SortedSet<string>(StringComparer.Ordinal);
List<string> list = GetListOfStrings();
list.Sort(StringComparer.Ordinal);

NLS davranışlarına geri dönme

Windows üzerinde çalışırken .NET 5+ uygulamalarını eski NLS davranışlarına geri döndürmek için .NET Genelleştirme ve ICU'daki adımları izleyin. Bu uygulama genelinde uyumluluk anahtarı, uygulama düzeyinde ayarlanmalıdır. Tek tek kitaplıklar bu davranışı kabul edemez veya geri çeviremez.

İpucu

Kod hijyenini iyileştirmeye ve mevcut gizli hataları keşfetmeye yardımcı olmak için CA1307, CA1309 ve CA1310 kod analizi kurallarını etkinleştirmenizi kesinlikle öneririz. Daha fazla bilgi için bkz . Kod çözümleyicilerini etkinleştirme.

Etkilenen API’ler

.NET 5'teki değişiklikler nedeniyle çoğu .NET uygulaması beklenmeyen davranışlarla karşılaşmaz. Ancak, etkilenen API'lerin sayısı ve bu API'lerin daha geniş .NET ekosistemi için ne kadar temel olduğuna bağlı olarak, .NET 5'in istenmeyen davranışlara neden olma veya uygulamanızda zaten var olan gizli hataları kullanıma sunma potansiyelinin farkında olmanız gerekir.

Etkilenen API'ler şunlardır:

Not

Bu, etkilenen API'lerin kapsamlı bir listesi değildir.

Yukarıdaki API'lerin tümü, varsayılan olarak iş parçacığının geçerli kültürünü kullanarak dilsel dize arama ve karşılaştırma kullanır. Dilsel ve sıralı arama ile karşılaştırma arasındaki farklar Sıralı ve dilsel arama ve karşılaştırmada vurgulanıyor.

ICU, NLS'den farklı dilsel dize karşılaştırmaları uyguladığından, .NET Core veya .NET Framework'ün önceki bir sürümünden .NET 5'e yükselten ve etkilenen API'lerden birini çağıran Windows tabanlı uygulamalar API'lerin farklı davranışlar göstermeye başladığına dikkat edebilir.

Özel durumlar

  • Bir API açık StringComparison veya CultureInfo parametre kabul ederse, bu parametre API'nin varsayılan davranışını geçersiz kılar.
  • System.Stringilk parametrenin türünde char olduğu üyeler (örneğin, String.IndexOf(Char)) çağıran veya InvariantCulture[IgnoreCase]belirten CurrentCulture[IgnoreCase] açık StringComparison bir bağımsız değişken geçirmediği sürece sıralı arama kullanır.

Her String API'nin varsayılan davranışının daha ayrıntılı bir analizi için Varsayılan arama ve karşılaştırma türleri bölümüne bakın.

Sıralı ve dilsel arama ve karşılaştırma karşılaştırması

Sıralı (dilsel olmayan olarak da bilinir) arama ve karşılaştırma, bir dizeyi tek tek char öğelerine ayırır ve char-by-char araması veya karşılaştırması yapar. Örneğin, iki dize tam olarak aynı karakter dizisini oluşturduğundan, dizeler "dog" ve "dog" karşılaştırıcı altında Ordinal eşit olarak karşılaştırır. Ancak karşılaştırıcının "dog""Dog" altında Ordinal eşit değil olarak karşılaştırın, çünkü bunlar tam olarak aynı karakter dizilerinden oluşmaz. Başka bir ifadeyle, büyük harf 'D''in kod noktası U+0044 küçük harf 'd''in kod noktasından U+0064önce gerçekleşir ve bu da "Dog" öncesinde "dog"sıralamayla sonuçlanır.

Karşılaştırıcı OrdinalIgnoreCase hala karakter karakter temelinde çalışır, ancak işlemi gerçekleştirirken büyük/küçük harf farklarını ortadan kaldırır. Bir OrdinalIgnoreCase karşılaştırıcı altında, karakter çiftleri ve 'D' karakter çiftleri 'Á''á''d' ile olduğu gibi eşit olarak karşılaştırır. Ancak, eşlenmeyen karakter, vurgulanmış karakterle 'a''á'eşit değil olarak karşılaştırır.

Bunun bazı örnekleri aşağıdaki tabloda verilmiştir:

Dize 1 Dize 2 Ordinal Karşılaştırma OrdinalIgnoreCase Karşılaştırma
"dog" "dog" eşittir eşittir
"dog" "Dog" eşit değil eşittir
"resume" "résumé" eşit değil eşit değil

Unicode ayrıca dizelerin birkaç farklı bellek içi gösterimine sahip olmasını sağlar. Örneğin, bir e-akut (é) iki olası şekilde temsil edilebilir:

  • Tek bir değişmez 'é' karakter (olarak da yazılır '\u00E9').
  • Sabit olmayan 'e' bir karakter ve ardından birleşen vurgu değiştirici karakteri '\u0301'.

Bu, aşağıdaki dört dizenin tümünün, kendi kurucu parçaları farklı olsa bile olarak "résumé"görüntülendiği anlamına gelir. Dizeler değişmez 'é' karakterlerin veya sabit değere sahip 'e' olmayan karakterlerin yanı sıra birleşen vurgu değiştiricisini '\u0301'kullanır.

  • "r\u00E9sum\u00E9"
  • "r\u00E9sume\u0301"
  • "re\u0301sum\u00E9"
  • "re\u0301sume\u0301"

Sıralı bir karşılaştırıcı altında, bu dizelerin hiçbiri birbiriyle eşit değildir. Bunun nedeni, hepsi farklı temel karakter dizileri içermeleridir, ancak bunlar ekranda işlendiğinde hepsi aynı görünür.

Bir string.IndexOf(..., StringComparison.Ordinal) işlem gerçekleştirilirken çalışma zamanı tam bir alt dize eşleşmesi arar. Sonuçlar aşağıdaki gibidir.

Console.WriteLine("resume".IndexOf("e", StringComparison.Ordinal)); // prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e", StringComparison.Ordinal)); // prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf("e", StringComparison.Ordinal)); // prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf("e", StringComparison.Ordinal)); // prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf("e", StringComparison.Ordinal)); // prints '1'
Console.WriteLine("resume".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf("E", StringComparison.OrdinalIgnoreCase)); // prints '1'

Sıralı arama ve karşılaştırma yordamları, geçerli iş parçacığının kültür ayarından hiçbir zaman etkilenmez.

Dilsel arama ve karşılaştırma yordamları bir dizeyi harmanlama öğelerine ayırır ve bu öğeler üzerinde aramalar veya karşılaştırmalar yapar. Bir dizenin karakterleriyle bileşen harmanlama öğeleri arasında 1:1 eşlemesi olması şart değildir. Örneğin, uzunluğu 2 olan bir dize yalnızca tek bir harmanlama öğesinden oluşabilir. İki dize dil bilgisine sahip bir şekilde karşılaştırıldığında, karşılaştırıcı dizenin değişmez karakterleri farklı olsa bile iki dizenin harmanlama öğelerinin aynı anlamsal anlama sahip olup olmadığını denetler.

Dizeyi "résumé" ve dört farklı gösterimini yeniden düşünün. Aşağıdaki tabloda her gösterimi harmanlama öğelerine ayrılmış olarak gösterilmektedir.

String Harmanlama öğeleri olarak
"r\u00E9sum\u00E9" "r" + "\u00E9" + "s" + "u" + "m" + "\u00E9"
"r\u00E9sume\u0301" "r" + "\u00E9" + "s" + "u" + "m" + "e\u0301"
"re\u0301sum\u00E9" "r" + "e\u0301" + "s" + "u" + "m" + "\u00E9"
"re\u0301sume\u0301" "r" + "e\u0301" + "s" + "u" + "m" + "e\u0301"

Harmanlama öğesi, okuyucuların tek bir karakter veya karakter kümesi olarak düşüneceği şeylere gevşek bir şekilde karşılık gelir. Kavramsal olarak grafeme kümesine benzer ancak biraz daha büyük bir şemsiyeyi kapsar.

Dil karşılaştırması altında tam eşleşmeler gerekli değildir. Harmanlama öğeleri bunun yerine anlamsal anlamlarına göre karşılaştırılır. Örneğin, dil karşılaştırıcısı alt dizeleri "\u00E9" eşit "e\u0301" olarak ele alır çünkü her ikisi de anlamsal olarak "akut aksan değiştiricisi olan küçük harfli e" anlamına gelir. Bu, yönteminin IndexOf"e\u0301" aşağıdaki kod örneğinde gösterildiği gibi, eşanlamlı olarak eşdeğer alt dizeyi "\u00E9"içeren daha büyük bir dize içindeki alt dizeyle eşleşmesini sağlar.

Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e")); // prints '-1' (not found)
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("\u00E9")); // prints '1'
Console.WriteLine("\u00E9".IndexOf("e\u0301")); // prints '0'

Bunun bir sonucu olarak, dil karşılaştırması kullanılıyorsa farklı uzunluklarda iki dize eşit olarak karşılaştırılabilir. Çağıranların, bu tür senaryolarda dize uzunluğuyla ilgilenen özel durum mantığına dikkat etmemesi gerekir.

Kültüre duyarlı arama ve karşılaştırma yordamları, dilsel arama ve karşılaştırma yordamlarının özel bir biçimidir. Kültüre duyarlı bir karşılaştırıcı altında harmanlama öğesi kavramı, belirtilen kültüre özgü bilgileri içerecek şekilde genişletilir.

Örneğin, Macar alfabesinde iki dz> karakteri <arka arkaya göründüğünde, d> veya <z'den <>ayrı kendi benzersiz harfleri olarak kabul edilirler. Bu, dz> bir dizede görüldüğünde, Macar kültüre duyarlı bir karşılaştırıcının bunu tek bir harmanlama öğesi olarak değerlendirdiği anlamına gelir<.

String Harmanlama öğeleri olarak Açıklamalar
"endz" "e" + "n" + "d" + "z" (standart dil karşılaştırıcı kullanarak)
"endz" "e" + "n" + "dz" (Macar kültürüne duyarlı bir karşılaştırıcı kullanarak)

Macar kültürüne duyarlı bir karşılaştırıcı kullanılırken, dz ve z farklı anlamsal anlamlara sahip harmanlama öğeleri olarak <kabul edildiğinden, dizenin "endz"alt dize "z"ile bitmediği anlamına gelir.><>

// Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU");
Console.WriteLine("endz".EndsWith("z")); // Prints 'False'

// Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine("endz".EndsWith("z")); // Prints 'True'

Not

  • Davranış: Dilbilimsel ve kültüre duyarlı karşılaştırıcılar zaman zaman davranışsal ayarlamalar yapabilir. Hem ICU hem de eski Windows NLS tesisi, dünya dillerinin nasıl değiştiğini dikkate almak için güncelleştirilir. Daha fazla bilgi için yerel ayar (kültür) veri değişim sıklığı blog gönderisine bakın. Tam bit düzeyinde arama ve karşılaştırma gerçekleştirdiğinden Sıra karşılaştırıcısının davranışı hiçbir zaman değişmez. Bununla birlikte , Unicode daha fazla karakter kümesini kapsayacak şekilde büyüdükçe ve mevcut büyük/küçük harf verilerindeki eksiklikleri düzelttiğinde OrdinalIgnoreCase karşılaştırıcısının davranışı değişebilir.
  • Kullanım: Karşılaştırıcılar StringComparison.InvariantCulture ve StringComparison.InvariantCultureIgnoreCase kültüre duyarlı olmayan dil karşılaştırıcılarıdır. Yani, bu karşılaştırıcılar vurgulanmış karakter é'nin birden çok olası temel gösterime sahip olması gibi kavramları anlar ve bu tür tüm gösterimlerin eşit olarak ele alınması gerekir. Ancak kültüre duyarlı olmayan dil karşılaştırıcıları, yukarıda gösterildiği gibi d veya <z'den <>farklı olarak dz>> için <özel işleme içermez. Ayrıca Almanca Eszett (ß) gibi özel harf karakterlerini kullanmaz.

.NET ayrıca sabit genelleştirme modunu da sunar. Bu kabul etme modu, dilsel arama ve karşılaştırma yordamlarıyla ilgilenen kod yollarını devre dışı bırakır. Bu modda, çağıranın sağladığı veya CultureInfo bağımsız değişkenden bağımsız olarak tüm işlemler Sıralı veya StringComparisonOrdinalIgnoreCase davranışlarını kullanır. Daha fazla bilgi için bkz . Genelleştirme için çalışma zamanı yapılandırma seçenekleri ve .NET Core Genelleştirme Sabit Modu.

Daha fazla bilgi için bkz . .NET'te dizeleri karşılaştırmak için en iyi yöntemler.

Güvenlik etkileri

Uygulamanız filtreleme için etkilenen bir API kullanıyorsa, sıralı arama yerine yanlışlıkla dil araması yapılmış olabilecek yerleri bulmaya yardımcı olmak için CA1307 ve CA1309 kod analizi kurallarını etkinleştirmenizi öneririz. Aşağıdaki gibi kod desenleri güvenlik açıklarına açık olabilir.

//
// THIS SAMPLE CODE IS INCORRECT.
// DO NOT USE IT IN PRODUCTION.
//
public bool ContainsHtmlSensitiveCharacters(string input)
{
    if (input.IndexOf("<") >= 0) { return true; }
    if (input.IndexOf("&") >= 0) { return true; }
    return false;
}

string.IndexOf(string) Yöntemi varsayılan olarak dil araması kullandığından, bir dizenin değişmez '<' değer veya '&' karakter içermesi ve yordamın string.IndexOf(string) döndürülmesi-1, arama alt dizesinin bulunamadığını gösterir. Kod çözümleme kuralları CA1307 ve CA1309 bu tür arama sitelerini işaretler ve geliştiriciyi olası bir sorun olduğu konusunda uyarır.

Varsayılan arama ve karşılaştırma türleri

Aşağıdaki tabloda, çeşitli dize ve dize benzeri API'ler için varsayılan arama ve karşılaştırma türleri listelenmiştir. Çağıran açık CultureInfo veya StringComparison parametre sağlarsa, bu parametre herhangi bir varsayılan değere göre kabul edilir.

API Varsayılan davranış Açıklamalar
string.Compare CurrentCulture
string.CompareTo CurrentCulture
string.Contains Sıralı
string.EndsWith Sıralı (ilk parametre bir charolduğunda )
string.EndsWith CurrentCulture (ilk parametre bir stringolduğunda )
string.Equals Sıralı
string.GetHashCode Sıralı
string.IndexOf Sıralı (ilk parametre bir charolduğunda )
string.IndexOf CurrentCulture (ilk parametre bir stringolduğunda )
string.IndexOfAny Sıralı
string.LastIndexOf Sıralı (ilk parametre bir charolduğunda )
string.LastIndexOf CurrentCulture (ilk parametre bir stringolduğunda )
string.LastIndexOfAny Sıralı
string.Replace Sıralı
string.Split Sıralı
string.StartsWith Sıralı (ilk parametre bir charolduğunda )
string.StartsWith CurrentCulture (ilk parametre bir stringolduğunda )
string.ToLower CurrentCulture
string.ToLowerInvariant Invariantculture
string.ToUpper CurrentCulture
string.ToUpperInvariant Invariantculture
string.Trim Sıralı
string.TrimEnd Sıralı
string.TrimStart Sıralı
string == string Sıralı
string != string Sıralı

API'lerin aksinestring, tüm MemoryExtensions API'ler aşağıdaki özel durumlar dışında sıralı aramaları ve karşılaştırmaları varsayılan olarak gerçekleştirir.

API Varsayılan davranış Açıklamalar
MemoryExtensions.ToLower CurrentCulture (null CultureInfo bağımsız değişken geçirildiğinde)
MemoryExtensions.ToLowerInvariant Invariantculture
MemoryExtensions.ToUpper CurrentCulture (null CultureInfo bağımsız değişken geçirildiğinde)
MemoryExtensions.ToUpperInvariant Invariantculture

Bunun bir sonucu, kodu tüketenden string kullanana ReadOnlySpan<char>dönüştürürken yanlışlıkla davranış değişikliklerinin ortaya çıkarılabilmesidir. Bunun bir örneği aşağıda verilmiştir.

string str = GetString();
if (str.StartsWith("Hello")) { /* do something */ } // this is a CULTURE-AWARE (linguistic) comparison

ReadOnlySpan<char> span = s.AsSpan();
if (span.StartsWith("Hello")) { /* do something */ } // this is an ORDINAL (non-linguistic) comparison

Bunu ele almak için önerilen yol, bu API'lere açık StringComparison bir parametre geçirmektir. Kod çözümleme kuralları CA1307 ve CA1309 bu konuda yardımcı olabilir.

string str = GetString();
if (str.StartsWith("Hello", StringComparison.Ordinal)) { /* do something */ } // ordinal comparison

ReadOnlySpan<char> span = s.AsSpan();
if (span.StartsWith("Hello", StringComparison.Ordinal)) { /* do something */ } // ordinal comparison

Ayrıca bkz.