Share via


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:

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:

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 vagy CultureInfo paramétert, az a paraméter felülírja az API alapértelmezett viselkedését.
  • System.Stringazokat a tagokat, String.IndexOf(Char)amelyekben az első paraméter típusa char (például ) sorszámos keresést használ, kivéve, ha a hívó egy explicit StringComparison argumentumot ad megCurrentCulture[IgnoreCase], vagy InvariantCulture[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+0064elő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 és StringComparison.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

Lásd még