.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:
- CA1307: Netlik için StringComparison belirtin
- CA1309: Sıralı StringComparison kullanın
- CA1310: Doğruluk için StringComparison belirtin
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:
- System.String.Compare
- System.String.EndsWith
- System.String.IndexOf
- System.String.StartsWith
- System.String.ToLower
- System.String.ToLowerInvariant
- System.String.ToUpper
- System.String.ToUpperInvariant
- System.Globalization.TextInfo (çoğu üye)
- System.Globalization.CompareInfo (çoğu üye)
- System.Array.Sort (dize dizilerini sıralarken)
- System.Collections.Generic.List<T>.Sort() (liste öğeleri dize olduğunda)
- System.Collections.Generic.SortedDictionary<TKey,TValue> (anahtarlar dize olduğunda)
- System.Collections.Generic.SortedList<TKey,TValue> (anahtarlar dize olduğunda)
- System.Collections.Generic.SortedSet<T> (küme dizeler içerdiğinde)
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
veyaCultureInfo
parametre kabul ederse, bu parametre API'nin varsayılan davranışını geçersiz kılar. System.String
ilk parametrenin türündechar
olduğu üyeler (örneğin, String.IndexOf(Char)) çağıran veyaInvariantCulture[IgnoreCase]
belirtenCurrentCulture[IgnoreCase]
açıkStringComparison
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
veStringComparison.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 StringComparison
OrdinalIgnoreCase 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 char olduğunda ) |
string.EndsWith |
CurrentCulture | (ilk parametre bir string olduğunda ) |
string.Equals |
Sıralı | |
string.GetHashCode |
Sıralı | |
string.IndexOf |
Sıralı | (ilk parametre bir char olduğunda ) |
string.IndexOf |
CurrentCulture | (ilk parametre bir string olduğunda ) |
string.IndexOfAny |
Sıralı | |
string.LastIndexOf |
Sıralı | (ilk parametre bir char olduğunda ) |
string.LastIndexOf |
CurrentCulture | (ilk parametre bir string olduğunda ) |
string.LastIndexOfAny |
Sıralı | |
string.Replace |
Sıralı | |
string.Split |
Sıralı | |
string.StartsWith |
Sıralı | (ilk parametre bir char olduğunda ) |
string.StartsWith |
CurrentCulture | (ilk parametre bir string olduğ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