Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A regex vagy regex egy sztring, amely lehetővé teszi a fejlesztő számára, hogy kifejezze a keresett mintát, így gyakori módszer a szövegben való keresésre és a találatok kinyerésére a keresett sztring részhalmazaként. A .NET-ben a System.Text.RegularExpressions
névtér a példányok és a statikus metódusok definiálására Regex , valamint a felhasználó által meghatározott mintákon való egyeztetésre szolgál. Ebben a cikkben megtudhatja, hogyan hozhat létre Regex
példányokat a teljesítmény optimalizálása érdekében a forrásgenerálás használatával.
Feljegyzés
Ahol lehetséges, használjon forrás által létrehozott reguláris kifejezéseket ahelyett, hogy reguláris kifejezéseket állít össze a RegexOptions.Compiled beállítással. A forráslétrehozás segíthet az alkalmazás gyorsabb indításában, gyorsabb futtatásában és a levághatóbb használatban. A forráslétrehozás lehetőségének megismeréséhez lásd : Mikor érdemes használni.
Lefordított reguláris kifejezések
Amikor ír new Regex("somepattern")
, néhány dolog történik. A megadott minta elemzése a minta érvényességének biztosítása és az elemzett regexet jelképező belső fává alakítása érdekében történik. A fa ezután különböző módokon van optimalizálva, és funkcionálisan egyenértékű változattá alakítja a mintát, amely hatékonyabban végrehajtható. A fa olyan formában van megírva, amely opkódok és operandusok sorozataként értelmezhető, amelyek útmutatást nyújtanak a regex értelmező motornak az egyezés módjára vonatkozóan. Egyezés végrehajtásakor az értelmező egyszerűen végigvezeti ezeket az utasításokat, és feldolgozja őket a bemeneti szövegen. Új Regex
példány példányának létrehozásakor vagy az egyik statikus metódus Regex
meghívásakor az értelmező az alapértelmezett motor.
Ha megadja RegexOptions.Compiled, az összes azonos építési idő alatt végzett munka lesz végrehajtva. Az így kapott utasításokat tovább alakítja a tükröződés-kibocsátó alapú fordító il utasításokká, amelyeket néhány DynamicMethod objektumra írnak. Egyezés végrehajtásakor a rendszer meghívja ezeket a metódusokat DynamicMethod
. Ez az IL lényegében pontosan azt teszi, amit az értelmező tenne, kivéve a pontos feldolgozásra specializálódott mintát. Ha például a minta tartalmaz [ac]
, az értelmező egy olyan opcode-t lát, amely azt jelzi, hogy "egyezik az aktuális pozícióban lévő bemeneti karakter az ebben a készlet leírásában megadott készlettel". Míg a lefordított IL olyan kódot tartalmazna, amely gyakorlatilag azt mondja, hogy "egyezzen az aktuális pozícióban lévő bemeneti karakterlel 'a'
, vagy 'c'
". Ez a speciális burkolat és a minta ismerete alapján történő optimalizálás képessége néhány fő oka annak, hogy a hozamok meghatározása sokkal gyorsabb átviteli sebességet eredményez, mint az értelmező RegexOptions.Compiled
.
Ennek több hátránya RegexOptions.Compiled
is van. A leghatásosabb az, hogy költséges felépíteni. Nem csak ugyanazokat a költségeket kell fizetnie, mint az értelmezőnek, hanem az eredményül RegexNode
kapott fát és az opcode-ok/operandusokat il-be kell fordítania, ami nem triviális költségeket ad hozzá. A létrehozott IL-t az első használatkor jiT-fordításra kell fordítani, ami még nagyobb költséggel jár az indításkor.
RegexOptions.Compiled
alapvető kompromisszumot jelent az első használat során járó többletterhelések és az azt követő használat többletterhelései között. A használat System.Reflection.Emit bizonyos környezetekben is gátolja a használatot RegexOptions.Compiled
; egyes operációs rendszerek nem teszik lehetővé a dinamikusan generált kód végrehajtását, és az ilyen rendszereken Compiled
nem működnek.
Forráslétrehozás
A .NET 7 új RegexGenerator
forrásgenerátort vezetett be. A forrásgenerátor olyan összetevő, amely csatlakoztatja a fordítót, és további forráskóddal bővíti a fordítási egységet. A .NET SDK (7-es és újabb verzió) tartalmaz egy forrásgenerátort, amely felismeri az GeneratedRegexAttribute attribútumot egy részleges metóduson, amely visszaadja Regex
. A forrásgenerátor a metódus implementálását biztosítja, amely tartalmazza a metódus összes logikáját Regex
. Előfordulhat például, hogy korábban a következőhöz hasonló kódot írt:
private static readonly Regex s_abcOrDefGeneratedRegex =
new(pattern: "abc|def",
options: RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static void EvaluateText(string text)
{
if (s_abcOrDefGeneratedRegex.IsMatch(text))
{
// Take action with matching text
}
}
A forrásgenerátor használatához írja át az előző kódot az alábbiak szerint:
[GeneratedRegex("abc|def", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex AbcOrDefGeneratedRegex();
private static void EvaluateText(string text)
{
if (AbcOrDefGeneratedRegex().IsMatch(text))
{
// Take action with matching text
}
}
Tipp.
A RegexOptions.Compiled
forrásgenerátor figyelmen kívül hagyja a jelölőt, ezért a forrás által létrehozott verzióban nincs rá szükség.
A létrehozott implementáció AbcOrDefGeneratedRegex()
hasonlóan gyorsítótáraz egy egytonos Regex
példányt, így a kód használatához nincs szükség további gyorsítótárazásra.
Az alábbi kép a forrás által létrehozott gyorsítótárazott példány képernyőfelvétele a internal
Regex
forrásgenerátor által kibocsátott alosztályra:
De mint látható, ez nem csak csinál new Regex(...)
. Ehelyett a forrásgenerátor C#-kódként bocsát ki egy egyéni Regex
származtatott implementációt, amely RegexOptions.Compiled
az IL-ben kibocsátotthoz hasonló logikával rendelkezik. Az átviteli sebesség minden előnyét RegexOptions.Compiled
(sőt, valójában) és az indítási Regex.CompileToAssembly
előnyöket is élvezheti, de a rendszer összetettsége CompileToAssembly
nélkül. A kibocsátott forrás a projekt része, ami azt jelenti, hogy könnyen megtekinthető és hibakeresésre alkalmas.
Tipp.
A Visual Studióban kattintson a jobb gombbal a részleges metódusdeklarációra, és válassza az Ugrás a definícióra lehetőséget. Másik lehetőségként válassza ki a projektcsomópontot a Megoldáskezelő, majd bontsa ki a Dependencies>Analyzers>System.Text.RegularExpressions.Generator>RegexGenerator.g.cs a regex generátorból létrehozott C#-kód megtekintéséhez.
Töréspontokat állíthat be benne, végiglépkedhet rajta, és tanulási eszközként használhatja, hogy pontosan megértse, hogyan dolgozza fel a regex motor a mintát a bemenetével. A generátor még tripla perjeles (XML) megjegyzéseket is generál, hogy a kifejezés egy pillantással érthető legyen, és hogy hol használják.
A forrás által létrehozott fájlokon belül
A .NET 7-tel a forrásgenerátor és RegexCompiler
szinte teljesen át lett írva, alapvetően megváltoztatva a létrehozott kód szerkezetét. Ezt a megközelítést kiterjesztették az összes szerkezet kezelésére (egyetlen kikötéssel), és a forrásgenerátor továbbra is RegexCompiler
többnyire 1:1-et képez egymással, az új megközelítést követve. Vegye figyelembe a forrásgenerátor kimenetét a kifejezés egyik elsődleges függvényéhez abc|def
:
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match with 2 alternative expressions, atomically.
{
if (slice.IsEmpty)
{
return false; // The input didn't match.
}
switch (slice[0])
{
case 'A' or 'a':
if ((uint)slice.Length < 3 ||
!slice.Slice(1).StartsWith("bc", StringComparison.OrdinalIgnoreCase)) // Match the string "bc" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 3;
slice = inputSpan.Slice(pos);
break;
case 'D' or 'd':
if ((uint)slice.Length < 3 ||
!slice.Slice(1).StartsWith("ef", StringComparison.OrdinalIgnoreCase)) // Match the string "ef" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 3;
slice = inputSpan.Slice(pos);
break;
default:
return false; // The input didn't match.
}
}
// The input matched.
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
A forrásként létrehozott kód célja, hogy érthető legyen, könnyen követhető szerkezettel, megjegyzésekkel, amelyek elmagyarázzák, hogy mi történik minden lépésben, és általában a kód kibocsátása az vezérelv szerint, hogy a generátornak úgy kell kódokat bocsátanak ki, mintha egy ember írta volna. A visszakövetés struktúrája akkor is a kód szerkezetének részévé válik, ha visszakövetésről van szó, ahelyett, hogy egy veremre támaszkodva jelezné, hogy hová kell ugrania. A következő például az azonos létrehozott egyező függvény kódja, ha a kifejezés a következő [ab]*[bc]
:
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
int charloop_starting_pos = 0, charloop_ending_pos = 0;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match a character in the set [ABab] greedily any number of times.
//{
charloop_starting_pos = pos;
int iteration = slice.IndexOfAnyExcept(Utilities.s_ascii_600000006000000);
if (iteration < 0)
{
iteration = slice.Length;
}
slice = slice.Slice(iteration);
pos += iteration;
charloop_ending_pos = pos;
goto CharLoopEnd;
CharLoopBacktrack:
if (Utilities.s_hasTimeout)
{
base.CheckTimeout();
}
if (charloop_starting_pos >= charloop_ending_pos ||
(charloop_ending_pos = inputSpan.Slice(charloop_starting_pos, charloop_ending_pos - charloop_starting_pos).LastIndexOfAny(Utilities.s_ascii_C0000000C000000)) < 0)
{
return false; // The input didn't match.
}
charloop_ending_pos += charloop_starting_pos;
pos = charloop_ending_pos;
slice = inputSpan.Slice(pos);
CharLoopEnd:
//}
// Advance the next matching position.
if (base.runtextpos < pos)
{
base.runtextpos = pos;
}
// Match a character in the set [BCbc].
if (slice.IsEmpty || ((uint)((slice[0] | 0x20) - 'b') > (uint)('c' - 'b')))
{
goto CharLoopBacktrack;
}
// The input matched.
pos++;
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
A kódban látható a visszakövetés struktúrája, CharLoopBacktrack
a visszakövetés goto
helyének és a regex egy későbbi részének meghibásodása esetén az adott helyre való ugráshoz használt címke.
Ha megtekinti a kód implementálását RegexCompiler
és a forrásgenerátort, azok rendkívül hasonlóak lesznek: hasonló nevű metódusok, hasonló hívásstruktúra és még hasonló megjegyzések is a megvalósítás során. A legtöbb esetben azonos kódot eredményeznek, bár egy IL-ben és egy C#-ban. Természetesen a C#-fordító feladata a C# il-re fordítása, így az eredményül kapott IL mindkét esetben valószínűleg nem lesz azonos. A forrásgenerátor erre támaszkodik különböző esetekben, kihasználva azt a tényt, hogy a C# fordító tovább optimalizálja a különböző C# szerkezeteket. Van néhány konkrét dolog, amit a forrásgenerátor így több optimalizált egyező kódot eredményez, mint a .RegexCompiler
Az előző példák egyikében például láthatja, hogy a forrásgenerátor egy kapcsolóutasítást bocsát ki, amelynek egyik ága 'a'
és egy másik ága van 'b'
. Mivel a C#-fordító nagyon jó a kapcsoló utasítások optimalizálásában, és több stratégia áll rendelkezésére a hatékony működéshez, a forrásgenerátor speciális optimalizálással rendelkezik, amely RegexCompiler
nem. A váltakozások esetében a forrásgenerátor az összes ágat megvizsgálja, és ha bizonyítani tudja, hogy minden ág egy másik kezdő karakterrel kezdődik, akkor egy kapcsolóutasítást bocsát ki az első karakter felett, és elkerüli az adott alternációhoz tartozó visszakövetési kód kimenetét.
Íme egy kicsit bonyolultabb példa erre. Az alternációkat jobban elemzik annak megállapításához, hogy lehetséges-e újrabontásuk oly módon, hogy könnyebben optimalizálják őket a háttérkövetési motorokkal, és ez egyszerűbb forráskódhoz vezet. Az egyik ilyen optimalizálás támogatja a gyakori előtagok kinyerését az ágakból, és ha a váltakozás atomi, akkor a rendezés nem számít, az ágak átrendezésével több ilyen kinyerést lehet lehetővé tenni. Ennek hatása a következő hétköznapi mintára Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday
mutat, amely az alábbihoz hasonló egyező függvényt hoz létre:
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
char ch;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match with 6 alternative expressions, atomically.
{
int alternation_starting_pos = pos;
// Branch 0
{
if ((uint)slice.Length < 6 ||
!slice.StartsWith("monday", StringComparison.OrdinalIgnoreCase)) // Match the string "monday" (ordinal case-insensitive)
{
goto AlternationBranch;
}
pos += 6;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 1
{
if ((uint)slice.Length < 7 ||
!slice.StartsWith("tuesday", StringComparison.OrdinalIgnoreCase)) // Match the string "tuesday" (ordinal case-insensitive)
{
goto AlternationBranch1;
}
pos += 7;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch1:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 2
{
if ((uint)slice.Length < 9 ||
!slice.StartsWith("wednesday", StringComparison.OrdinalIgnoreCase)) // Match the string "wednesday" (ordinal case-insensitive)
{
goto AlternationBranch2;
}
pos += 9;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch2:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 3
{
if ((uint)slice.Length < 8 ||
!slice.StartsWith("thursday", StringComparison.OrdinalIgnoreCase)) // Match the string "thursday" (ordinal case-insensitive)
{
goto AlternationBranch3;
}
pos += 8;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch3:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 4
{
if ((uint)slice.Length < 6 ||
!slice.StartsWith("fr", StringComparison.OrdinalIgnoreCase) || // Match the string "fr" (ordinal case-insensitive)
((((ch = slice[2]) | 0x20) != 'i') & (ch != 'İ')) || // Match a character in the set [Ii\u0130].
!slice.Slice(3).StartsWith("day", StringComparison.OrdinalIgnoreCase)) // Match the string "day" (ordinal case-insensitive)
{
goto AlternationBranch4;
}
pos += 6;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch4:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 5
{
// Match a character in the set [Ss].
if (slice.IsEmpty || ((slice[0] | 0x20) != 's'))
{
return false; // The input didn't match.
}
// Match with 2 alternative expressions, atomically.
{
if ((uint)slice.Length < 2)
{
return false; // The input didn't match.
}
switch (slice[1])
{
case 'A' or 'a':
if ((uint)slice.Length < 8 ||
!slice.Slice(2).StartsWith("turday", StringComparison.OrdinalIgnoreCase)) // Match the string "turday" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 8;
slice = inputSpan.Slice(pos);
break;
case 'U' or 'u':
if ((uint)slice.Length < 6 ||
!slice.Slice(2).StartsWith("nday", StringComparison.OrdinalIgnoreCase)) // Match the string "nday" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 6;
slice = inputSpan.Slice(pos);
break;
default:
return false; // The input didn't match.
}
}
}
AlternationMatch:;
}
// The input matched.
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
Ugyanakkor a forrásgenerátornak más problémái is vannak, amelyekkel egyszerűen nem lehet felvenni a kérdést, amikor közvetlenül az IL-nek ad ki kimenetet. Ha visszanéz néhány példakódra, láthat néhány zárójelet, amely kissé furcsán megjegyzést fűzött hozzá. Ez nem hiba. A forrásgenerátor felismeri, hogy ha ezeket a zárójeleket nem fűzték megjegyzéshez, a visszakövetés struktúrája a hatókörön kívülről a hatókörön belül definiált címkére való ugrásra támaszkodik; egy ilyen címke nem lenne látható egy goto
ilyen címkén, és a kód fordítása sikertelen lenne. Így a forrásgenerátornak el kell kerülnie, hogy hatókör legyen az útban. Bizonyos esetekben egyszerűen megjegyzést fűz a hatókörhöz, ahogy itt is tették. Más esetekben, amikor ez nem lehetséges, néha elkerülheti azokat a szerkezeteket, amelyek hatóköröket (például többutas if
blokkot) igényelnek, ha ez problémás lenne.
A forrásgenerátor egyetlen kivétellel minden RegexCompiler
fogópontot kezel. A kezeléshez RegexOptions.IgnoreCase
hasonlóan az implementációk most is egy casing táblát használnak a készletek létrehozásához az építkezés során, és hogy a háttérrendszerek egyeztetésének hogyan IgnoreCase
kell áttekintenie ezt a casing táblát. Ez a tábla a szerelvényen kívüli kód (beleértve a forrásgenerátor által kibocsátott kódot is) belső System.Text.RegularExpressions.dll
része, és egyelőre nem rendelkezik hozzáféréssel. Így a háttérrendszerek kezelése IgnoreCase
kihívást jelent a forrásgenerátorban, és nem támogatottak. Ez az egyetlen szerkezet, amelyet a forrásgenerátor nem támogat RegexCompiler
. Ha olyan mintát próbál használni, amely ezek egyikével rendelkezik (ami ritka), a forrásgenerátor nem bocsát ki egyéni implementációt, és ehelyett vissza fog esni egy normál Regex
példány gyorsítótárazásához:
Továbbá sem a forrásgenerátor nem RegexCompiler
támogatja az új RegexOptions.NonBacktracking
. Ha megadja RegexOptions.Compiled | RegexOptions.NonBacktracking
, a rendszer figyelmen kívül hagyja a Compiled
jelölőt, és ha NonBacktracking
megadja a forrásgenerátort, az is visszaesik egy normál Regex
példány gyorsítótárazásához.
Mikor lehet használni
Az általános útmutató az, ha használhatja a forrásgenerátort, használja azt. Ha a mai napot c# nyelven használja Regex
a fordításkor ismert argumentumokkal, és különösen akkor, ha már használja RegexOptions.Compiled
(mivel a regexet olyan gyakori helyként azonosították, amely kihasználná a gyorsabb átviteli sebességet), érdemes inkább a forrásgenerátort használnia. A forrásgenerátor a regexnek a következő előnyöket nyújtja:
- Az átviteli sebesség minden előnye
RegexOptions.Compiled
. - Az indítás előnyei, hogy nem kell minden regex elemzést, elemzést és fordítást futtatni.
- Lehetőség az idő előtti fordítás használatára a regexhez létrehozott kóddal.
- A regex jobb hibakeresése és megértése.
- Lehetőség a levágott alkalmazás méretének csökkentésére, ha levágja a kódhoz társított
RegexCompiler
nagy kódot (és akár a tükröződést is kibocsátja).
Ha olyan beállítással használják, mint RegexOptions.NonBacktracking
amelynél a forrásgenerátor nem tud egyéni implementációt létrehozni, akkor is gyorsítótárazást és XML-megjegyzéseket bocsát ki, amelyek leírják az implementációt, így értékesek lesznek. A forrásgenerátor fő hátránya, hogy további kódot bocsát ki a szerelvénybe, így nagyobb méretre lehet szükség. Minél több regex van az alkalmazásban, és minél nagyobbak, annál több kód jelenik meg számukra. Bizonyos helyzetekben, akárcsak RegexOptions.Compiled
szükségtelen, a forrásgenerátor is lehet. Ha például olyan regexe van, amelyre csak ritkán van szükség, és amelynek átviteli sebessége nem számít, előnyösebb lehet, ha csak az értelmezőre támaszkodik a szórványos használathoz.
Fontos
A .NET 7 tartalmaz egy elemzőt , amely azonosítja a forrásgenerátorsá konvertálható használatot Regex
, valamint egy olyan javítót, amely elvégzi az átalakítást: