Viselkedésváltozások a .NET 5+ sztringjeinek összehasonlításakor
A .NET 5 olyan futtatókörnyezeti viselkedésváltozást vezet be, amelyben a globalizációs API-k alapértelmezés szerint az ICU-t használják az összes támogatott platformon. Ez eltér a .NET Core korábbi verzióitól és a .NET-keretrendszer, amelyek windowsos futtatáskor az operációs rendszer nemzeti nyelvi támogatási (NLS) funkcióit használják. További információ ezekről a változásokról, beleértve a viselkedésváltozás visszaállítására képes kompatibilitási kapcsolókat, lásd a .NET-globalizációt és az intenzív osztályt.
A változás oka
Ez a módosítás az egységesítés érdekében lett bevezetve . A NET globalizációs viselkedése az összes támogatott operációs rendszeren. Emellett lehetővé teszi az alkalmazások számára, hogy saját globalizációs kódtárakat csomagoljanak össze, nem pedig az operációs rendszer beépített kódtáraitól. További információt a kompatibilitástörő változásról szóló értesítésben talál.
Viselkedésbeli különbségek
Ha olyan függvényeket használ, mint string.IndexOf(string)
az argumentumot StringComparison igénylő túlterhelés meghívása nélkül, érdemes lehet egy sorszámozott keresést végezni, de ehelyett véletlenül függőséget vállal a kultúraspecifikus viselkedéshez. Mivel az NLS és az ICU különböző logikát implementál a nyelvi összehasonlítóikban, az ilyen string.IndexOf(string)
módszerek eredményei váratlan értékeket adhatnak vissza.
Ez olyan helyeken is megnyilvánulhat, ahol nem mindig számít arra, hogy a globalizációs létesítmények aktívak lesznek. Az alábbi kód például az aktuális futtatókörnyezettől függően eltérő választ adhat.
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)
További információ: Globalization API-k ICU-kódtárakat használnak Windows rendszeren.
A váratlan viselkedés elleni védelem
Ez a szakasz két lehetőséget biztosít a .NET 5 váratlan viselkedésváltozásainak kezelésére.
Kódelemzők engedélyezése
A kódelemzők észlelhetik a hibás hívási webhelyeket. A meglepő viselkedés elleni védelem érdekében javasoljuk, hogy engedélyezze a .NET-fordítóplatformok (Roslyn) elemzőit a projektben. Az elemzők segítenek megjelölni azokat a kódot, amelyek véletlenül nyelvi összehasonlítót használnak, amikor valószínűleg egy sorszám-összehasonlítót szántak. A következő szabályok segítenek megjelölni ezeket a problémákat:
- CA1307: StringComparison megadása az egyértelműség érdekében
- CA1309: Az ordinal StringComparison használata
- CA1310: StringComparison megadása a helyesség érdekében
Ezek a szabályok alapértelmezés szerint nincsenek engedélyezve. Ha engedélyezni szeretné őket, és buildelési hibaként szeretné megjeleníteni a szabálysértéseket, állítsa be a következő tulajdonságokat a projektfájlban:
<PropertyGroup>
<AnalysisMode>All</AnalysisMode>
<WarningsAsErrors>$(WarningsAsErrors);CA1307;CA1309;CA1310</WarningsAsErrors>
</PropertyGroup>
Az alábbi kódrészlet olyan kódpéldákat mutat be, amelyek a megfelelő kódelemző figyelmeztetéseket vagy hibákat eredményeznek.
//
// 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);
Hasonlóképpen, a sztringek rendezett gyűjteményének példányosításakor vagy egy meglévő sztringalapú gyűjtemény rendezésekor adjon meg egy explicit összehasonlítót.
//
// 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);
Vissza az NLS-viselkedésre
Ha vissza szeretné állítani a .NET 5+ alkalmazásokat a régebbi NLS-viselkedésekre Windows rendszeren való futtatáskor, kövesse a .NET-globalizáció és az ICU lépéseit. Ezt az alkalmazásszintű kompatibilitási kapcsolót az alkalmazás szintjén kell beállítani. Az egyes kódtárak nem tudnak bejelentkezni vagy kikapcsolni ezt a viselkedést.
Tipp.
Határozottan javasoljuk, hogy engedélyezze a CA1307, CA1309 és CA1310 kódelemzési szabályokat a kódhigiénia javítása és a meglévő látens hibák felderítése érdekében. További információ: Kódelemzők engedélyezése.
Érintett API-k
A legtöbb .NET-alkalmazás nem fog váratlan viselkedést tapasztalni a .NET 5 változásai miatt. Az érintett API-k száma és az API-k alapszintűsége miatt azonban tisztában kell lennie azzal, hogy a .NET 5 nem kívánt viselkedést válthat ki, vagy rejtett hibákat tehet közzé az alkalmazásban.
Az érintett API-k a következők:
- 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 (a legtöbb tag)
- System.Globalization.CompareInfo (a legtöbb tag)
- System.Array.Sort (sztringtömbök rendezésekor)
- System.Collections.Generic.List<T>.Sort() (ha a listaelemek sztringek)
- System.Collections.Generic.SortedDictionary<TKey,TValue> (ha a kulcsok sztringek)
- System.Collections.Generic.SortedList<TKey,TValue> (ha a kulcsok sztringek)
- System.Collections.Generic.SortedSet<T> (ha a készlet sztringeket tartalmaz)
Feljegyzés
Ez nem az érintett API-k teljes listája.
A fenti API-k alapértelmezés szerint nyelvi sztringkeresést és összehasonlítást használnak a szál aktuális kultúrájával. A nyelvi és a sorszámos keresés és az összehasonlítás közötti különbségeket az Ordinal vs. nyelvi keresés és összehasonlítás ismerteti.
Mivel az ICU az NLS-től eltérő nyelvi sztring-összehasonlításokat valósít meg, a .NET Core vagy .NET-keretrendszer korábbi verziójáról .NET 5-re frissített Windows-alapú alkalmazások észrevehetik, hogy az API-k eltérő viselkedést mutatnak.
Kivételek
- Ha egy API elfogad egy explicit
StringComparison
vagyCultureInfo
paramétert, az a paraméter felülírja az API alapértelmezett viselkedését. System.String
azokat a tagokat, String.IndexOf(Char)amelyekben az első paraméter típusachar
(például ) sorszámos keresést használ, kivéve, ha a hívó egy explicitStringComparison
argumentumot ad megCurrentCulture[IgnoreCase]
, vagyInvariantCulture[IgnoreCase]
.
Az egyes String API-k alapértelmezett viselkedésének részletesebb elemzéséhez tekintse meg az Alapértelmezett keresési és összehasonlítási típusok szakaszt.
Ordinal vs. nyelvi keresés és összehasonlítás
Az ordinális (más néven nem nyelvi) keresés és összehasonlítás a sztringeket az egyes char
elemekre bontja, és char-by-char keresést vagy összehasonlítást hajt végre. A sztringek "dog"
és "dog"
a összehasonlítása például egyenlő egy Ordinal
összehasonlító alatt, mivel a két sztring pontosan azonos karaktersorozatból áll. Azonban, "dog"
és "Dog"
hasonlítsa össze, mint nem egyenlő egy Ordinal
összehasonlító, mert nem áll pontosan ugyanazt a sorozatot a karakter. Ez azt jelenti, hogy a nagybetűs 'D'
kódpont U+0044
a kisbetűs 'd'
kódpont U+0064
előtt következik be, ami korábban "dog"
rendezést "Dog"
eredményez.
A OrdinalIgnoreCase
összehasonlító továbbra is karakterenkénti alapon működik, de kiküszöböli a kis- és nagybetűk közötti különbségeket a művelet végrehajtása során. OrdinalIgnoreCase
Egy összehasonlító alatt a karakterpárok 'd'
és 'D'
a összehasonlítása egyenlő, ahogy a karakterpárok 'á'
és 'Á'
a . A nem szagolt karakter 'a'
azonban nem egyenlő az ékezetes karakterekkel 'á'
.
Erre néhány példát az alábbi táblázat tartalmaz:
1. sztring | 2. sztring | Ordinal összehasonlítás |
OrdinalIgnoreCase összehasonlítás |
---|---|---|---|
"dog" |
"dog" |
egyenlő | egyenlő |
"dog" |
"Dog" |
nem egyenlő | egyenlő |
"resume" |
"résumé" |
nem egyenlő | nem egyenlő |
A Unicode lehetővé teszi, hogy a sztringek több különböző memóriabeli ábrázolásokkal rendelkezzenek. Az e-akut (é) például kétféleképpen ábrázolható:
- Egyetlen literális
'é'
karakter (más néven'\u00E9'
). - Egy literális nem konstans
'e'
karakter, amelyet egy ékezetmódosító karakter'\u0301'
kombinálása követ.
Ez azt jelenti, hogy a következő négy sztring mind úgy jelenik meg, mint "résumé"
annak ellenére, hogy azok alkotórészei eltérőek. A sztringek a literális 'é'
karakterek vagy a nem konstans karakterek kombinációját 'e'
használják, valamint az ékezetmódosítót '\u0301'
.
"r\u00E9sum\u00E9"
"r\u00E9sume\u0301"
"re\u0301sum\u00E9"
"re\u0301sume\u0301"
A sorszám-összehasonlítók között egyik sztring sem egyezik egymással. Ennek az az oka, hogy mindegyik különböző mögöttes karaktersorozatokat tartalmaz, annak ellenére, hogy a képernyőre renderelve mindegyik ugyanúgy néz ki.
Művelet string.IndexOf(..., StringComparison.Ordinal)
végrehajtásakor a futtatókörnyezet pontos részszűrési egyezést keres. Az eredmények a következők.
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'
A szokásos keresési és összehasonlító rutinokat soha nem befolyásolja az aktuális szál kulturális beállítása.
A nyelvi keresési és összehasonlító rutinok rendezési elemekre bontanak egy sztringet, és ezeken az elemeken végeznek keresést vagy összehasonlításokat. Nem feltétlenül van 1:1 leképezés a sztring karakterei és az azt alkotó rendezési elemek között. Egy 2. hosszúságú sztring például csak egyetlen rendezési elemből állhat. Ha két sztringet hasonlít össze nyelvtudatos módon, az összehasonlító ellenőrzi, hogy a két sztring rendezési elemei azonos szemantikai jelentéssel rendelkeznek-e, még akkor is, ha a sztring literális karakterei eltérőek.
Fontolja meg újra a sztringet "résumé"
és annak négy különböző ábrázolásait. Az alábbi táblázat az egyes ábrázolásokat rendezési elemekre bontva mutatja be.
Sztring | Rendezési elemekként |
---|---|
"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" |
A rendezési elem lazán megfelel annak, amit az olvasók egyetlen karakternek vagy karakterhalmaznak gondolnának. Fogalmilag hasonló a gráffürthöz, de egy valamivel nagyobb esernyőt foglal magában.
Nyelvi összehasonlító esetén nincs szükség pontos egyezésekre. A rendezési elemeket inkább szemantikai jelentésük alapján hasonlítják össze. A nyelvi összehasonlítók például egyenlőként kezelik a részszűkítéseket "\u00E9"
"e\u0301"
, mivel mindkettő szemantikailag "egy kisbetűt és egy akut hangsúlymódosítót" jelent. Ez lehetővé teszi, hogy a IndexOf
metódus egy nagyobb sztringen belül egyezzen a szemantikailag egyenértékű részsztringgel "e\u0301"
"\u00E9"
, ahogy az az alábbi kódmintában látható.
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'
Ennek következtében két különböző hosszúságú sztring egyenlő lehet, ha nyelvi összehasonlítást használ. A hívónak ügyelnie kell arra, hogy az ilyen helyzetekben a sztringhosszsal foglalkozó speciális esetlogikát ne alkalmazzák.
A kultúratudatos keresési és összehasonlító rutinok a nyelvi keresési és összehasonlító rutinok speciális formái. A kultúratudatos összehasonlítók esetében a rendezési elem fogalma ki van terjesztve a megadott kultúrára jellemző információkra.
A magyar ábécében például, amikor a két karakter <dz> visszafelé jelenik meg, a d vagy <z> betűtől <> eltérő egyedi betűnek számít. Ez azt jelenti, hogy amikor <egy sztringben dz> látható, a magyar kultúratudatos összehasonlító egyetlen rendezési elemként kezeli.
Sztring | Rendezési elemekként | Megjegyzések |
---|---|---|
"endz" |
"e" + "n" + "d" + "z" |
(szabványos nyelvi összehasonlító használatával) |
"endz" |
"e" + "n" + "dz" |
(magyar kultúratudatos összehasonlító használata) |
Magyar kultúratudatos összehasonlító használata esetén ez azt jelenti, hogy a sztring "endz"
nem végződik az alsztringgel "z"
, mivel <a dz> és <a z> különböző szemantikai jelentésű rendezési elemeknek minősülnek.
// 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'
Feljegyzés
- Viselkedés: A nyelvi és kultúratudatos összehasonlítók időnként viselkedésbeli módosításokat hajthatnak végre. Az ICU és a régebbi Windows NLS-eszköz is frissül, hogy figyelembe vegyék a világnyelvek változását. További információkért lásd a Területi beállítások (kultúra) adatváltozás című blogbejegyzést. Az Ordinal comparer viselkedése soha nem változik, mivel pontosan bitenkénti keresést és összehasonlítást végez. Az OrdinalIgnoreCase összehasonlító viselkedése azonban megváltozhat, mivel a Unicode egyre több karakterkészletet foglal magában, és kijavítja a meglévő burkolatadatok kihagyását.
- Használat: Az összehasonlítók
StringComparison.InvariantCulture
ésStringComparison.InvariantCultureIgnoreCase
nyelvi összehasonlítók, amelyek nem kultúratudatosak. Ez azt jelzi, hogy ezek az összehasonlítók olyan fogalmakat ismernek, mint például az ékezetes karakter, amelynek több lehetséges mögöttes ábrázolása van, és hogy minden ilyen ábrázolás egyenlő. A nem kultúratudatos nyelvi összehasonlítók azonban nem tartalmaznak speciális kezelést a dz-hez <> a d-től és <a> z-től <>eltérő módon, ahogy az fent látható. Emellett nem lesznek olyan speciális karakterek, mint a német Eszett (ß).
A .NET az invariáns globalizációs módot is kínálja. Ez az opt-in mód letiltja a nyelvi keresési és összehasonlító rutinokkal foglalkozó kódútvonalakat. Ebben a módban minden művelet ordinali vagy OrdinalIgnoreCase viselkedést használ, függetlenül attól, hogy a hívó mit CultureInfo
vagy StringComparison
argumentumot biztosít. További információ: Futtatókörnyezeti konfigurációs beállítások a globalizációhoz és a .NET Core Globalization Invariant Módhoz.
További információ: Ajánlott eljárások a sztringek összehasonlításához a .NET-ben.
Biztonsági következmények
Ha az alkalmazás egy érintett API-t használ a szűréshez, javasoljuk, hogy engedélyezze a CA1307- és CA1309-kódelemzési szabályokat, hogy könnyebben megtalálhassa azokat a helyeket, ahol a nyelvi keresést véletlenül használták a szabályszerű keresés helyett. Az alábbihoz hasonló kódminták érzékenyek lehetnek a biztonsági résekre.
//
// 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;
}
Mivel a string.IndexOf(string)
módszer alapértelmezés szerint nyelvi keresést használ, lehetséges, hogy egy sztring literális '<'
vagy '&'
karaktert tartalmaz, és a string.IndexOf(string)
rutin visszatér -1
, ami azt jelzi, hogy a keresési részsztring nem található. A CA1307 és a CA1309 kódelemzési szabályok megjelölik az ilyen hívási helyeket, és figyelmeztetik a fejlesztőt, hogy lehetséges probléma merül fel.
Alapértelmezett keresési és összehasonlítási típusok
Az alábbi táblázat a különböző sztringekhez és sztringszerű API-khoz tartozó alapértelmezett keresési és összehasonlító típusokat sorolja fel. Ha a hívó explicit CultureInfo
vagy StringComparison
paramétert ad meg, a paramétert a rendszer minden alapértelmezett értéken tiszteletben tartja.
API | Alapértelmezett viselkedés | Megjegyzések |
---|---|---|
string.Compare |
CurrentCulture | |
string.CompareTo |
CurrentCulture | |
string.Contains |
Sorszám | |
string.EndsWith |
Sorszám | (ha az első paraméter a char ) |
string.EndsWith |
CurrentCulture | (ha az első paraméter a string ) |
string.Equals |
Sorszám | |
string.GetHashCode |
Sorszám | |
string.IndexOf |
Sorszám | (ha az első paraméter a char ) |
string.IndexOf |
CurrentCulture | (ha az első paraméter a string ) |
string.IndexOfAny |
Sorszám | |
string.LastIndexOf |
Sorszám | (ha az első paraméter a char ) |
string.LastIndexOf |
CurrentCulture | (ha az első paraméter a string ) |
string.LastIndexOfAny |
Sorszám | |
string.Replace |
Sorszám | |
string.Split |
Sorszám | |
string.StartsWith |
Sorszám | (ha az első paraméter a char ) |
string.StartsWith |
CurrentCulture | (ha az első paraméter a string ) |
string.ToLower |
CurrentCulture | |
string.ToLowerInvariant |
InvariantCulture | |
string.ToUpper |
CurrentCulture | |
string.ToUpperInvariant |
InvariantCulture | |
string.Trim |
Sorszám | |
string.TrimEnd |
Sorszám | |
string.TrimStart |
Sorszám | |
string == string |
Sorszám | |
string != string |
Sorszám |
Az API-któl eltérően string
az összes MemoryExtensions
API alapértelmezés szerint ordinális kereséseket és összehasonlításokat végez az alábbi kivételekkel.
API | Alapértelmezett viselkedés | Megjegyzések |
---|---|---|
MemoryExtensions.ToLower |
CurrentCulture | (ha null CultureInfo argumentumot ad át) |
MemoryExtensions.ToLowerInvariant |
InvariantCulture | |
MemoryExtensions.ToUpper |
CurrentCulture | (ha null CultureInfo argumentumot ad át) |
MemoryExtensions.ToUpperInvariant |
InvariantCulture |
Ennek az a következménye, hogy ha a kódot a felhasználásról string
a fogyasztásra ReadOnlySpan<char>
konvertálja, a viselkedésbeli változások véletlenül is megjelenhetnek. Erre egy példa a következő.
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
Ennek a megoldásának ajánlott módja, ha explicit StringComparison
paramétert ad át ezeknek az API-knak. A CA1307 és CA1309 kódelemzési szabályok segíthetnek ebben.
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