Share via


.NET reguláris kifejezésforrás-generátorok

A regex vagy regex egy sztring, amely lehetővé teszi a fejlesztő számára, hogy kifejezze a keresett mintát, így nagyon gyakori módja a szövegben való keresésnek, és az eredmények kinyerése 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 definiált mintákra 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

Ha lehetséges, használja a forrás által létrehozott reguláris kifejezéseket ahelyett, hogy reguláris kifejezéseket állít össze a RegexOptions.Compiled beállítás használatával. A forráslétrehozás segíthet az alkalmazás gyorsabb elindításában, gyorsabb futtatásában és a vágásban. 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 Regexmeghívásakor az értelmező az alapértelmezett motor.

Ha megadja RegexOptions.Compiled, a rendszer ugyanazt az építési időt fogja elvégezni. Az így kapott utasításokat tovább alakítaná a tükröződés-kibocsátó-alapú fordító il utasításokká, amelyeket néhány DynamicMethods-ra írnának. Ha egyezést hajtottak végre, a rendszer meghívja azokat DynamicMethod. Ez a il lényegében azt tenné, 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-ot lát, amely azt mondja, hogy "egyezik a bemeneti karakter az aktuális pozícióban 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 a bemeneti karaktersel 'a' az aktuális pozícióban, vagy 'c'". Ez a speciális burkolat és a minta ismerete alapján történő optimalizálás képessége a hozamok sokkal gyorsabb egyező átviteli sebességének RegexOptions.Compiled meghatározásának fő oka, mint az értelmező.

Ennek több hátránya RegexOptions.Compiledis van. A leghatásosabb az, hogy sokkal több építési költséggel jár, mint az értelmező használata. Nem csak ugyanazokat a költségeket kell fizetnie, mint az értelmezőnek, de ezt az eredményül kapott RegexNode fát kell lefordítani, és opcode-ok/operandusokat generálni az IL-be, ami nem triviális költségeket ad hozzá. A generált 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. Amikor a C#-fordítót "Roslyn" C#-fordítóként írták át, a teljes fordítási folyamat objektummodelljeit, valamint elemzőket is közzétett. Újabban a Roslyn által engedélyezett forrásgenerátorok. Az elemzőhöz hasonlóan a forrásgenerátor is olyan összetevő, amely csatlakoztatja a fordítót, és minden információt átad az elemzőnek, de a diagnosztikán kívül további forráskódokkal is bővítheti a fordítási egységet. A .NET 7+ SDK tartalmaz egy új forrásgenerátort, amely felismeri az újat GeneratedRegexAttribute egy részleges metóduson, amely visszaadja Regex. A forrásgenerátor ennek a metódusnak a implementációját biztosítja, amely a Regex. Előfordulhat például, hogy 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
    }
}

Az előző kódot a következőképpen írhatja át:

[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
    }
}

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.

Tipp.

A RegexOptions.Compiled jelölőt a forrásgenerátor figyelmen kívül hagyja, így már nincs rá szükség a forrás által létrehozott verzióban.

Az alábbi kép a forrás által létrehozott gyorsítótárazott példány képernyőfelvétele a internalRegex forrásgenerátor által kibocsátott alosztályra:

Gyorsítótárazott regex statikus mező

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 Regexszá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.CompileToAssemblyelőnyöket is élvezheti, de a rendszer összetettsége CompileToAssemblynélkül. A kibocsátott forrás a projekt része, ami azt jelenti, hogy könnyen megtekinthető és hibakeresésre alkalmas.

Hibakeresés a forrás által létrehozott Regex-kódon keresztül

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>System.Text.RegularExpressions.Generator.RegexGenerator>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.

Regexet leíró XML-megjegyzések létrehozása

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 (a|bc)d :

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;
}
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    int capture_starting_pos = 0;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // 1st capture group.
    //{
        capture_starting_pos = pos;

        // Match with 2 alternative expressions.
        //{
            if (slice.IsEmpty)
            {
                UncaptureUntil(0);
                return false; // The input didn't match.
            }

            switch (slice[0])
            {
                case 'a':
                    pos++;
                    slice = inputSpan.Slice(pos);
                    break;

                case 'b':
                    // Match 'c'.
                    if ((uint)slice.Length < 2 || slice[1] != 'c')
                    {
                        UncaptureUntil(0);
                        return false; // The input didn't match.
                    }

                    pos += 2;
                    slice = inputSpan.Slice(pos);
                    break;

                default:
                    UncaptureUntil(0);
                    return false; // The input didn't match.
            }
        //}

        base.Capture(1, capture_starting_pos, pos);
    //}

    // Match 'd'.
    if (slice.IsEmpty || slice[0] != 'd')
    {
        UncaptureUntil(0);
        return false; // The input didn't match.
    }

    // The input matched.
    pos++;
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;

    // <summary>Undo captures until it reaches the specified capture position.</summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    void UncaptureUntil(int capturePosition)
    {
        while (base.Crawlpos() > capturePosition)
        {
            base.Uncapture();
        }
    }
}

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;
}
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("ABab");
        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("BCbc")) < 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|Sundaymutat, 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;
}
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // Match with 5 alternative expressions, atomically.
    {
        if (slice.IsEmpty)
        {
            return false; // The input didn't match.
        }

        switch (slice[0])
        {
            case 'M':
                // Match the string "onday".
                if (!slice.Slice(1).StartsWith("onday"))
                {
                    return false; // The input didn't match.
                }

                pos += 6;
                slice = inputSpan.Slice(pos);
                break;

            case 'T':
                // Match with 2 alternative expressions, atomically.
                {
                    if ((uint)slice.Length < 2)
                    {
                        return false; // The input didn't match.
                    }

                    switch (slice[1])
                    {
                        case 'u':
                            // Match the string "esday".
                            if (!slice.Slice(2).StartsWith("esday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 7;
                            slice = inputSpan.Slice(pos);
                            break;

                        case 'h':
                            // Match the string "ursday".
                            if (!slice.Slice(2).StartsWith("ursday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 8;
                            slice = inputSpan.Slice(pos);
                            break;

                        default:
                            return false; // The input didn't match.
                    }
                }

                break;

            case 'W':
                // Match the string "ednesday".
                if (!slice.Slice(1).StartsWith("ednesday"))
                {
                    return false; // The input didn't match.
                }

                pos += 9;
                slice = inputSpan.Slice(pos);
                break;

            case 'F':
                // Match the string "riday".
                if (!slice.Slice(1).StartsWith("riday"))
                {
                    return false; // The input didn't match.
                }

                pos += 6;
                slice = inputSpan.Slice(pos);
                break;

            case 'S':
                // Match with 2 alternative expressions, atomically.
                {
                    if ((uint)slice.Length < 2)
                    {
                        return false; // The input didn't match.
                    }

                    switch (slice[1])
                    {
                        case 'a':
                            // Match the string "turday".
                            if (!slice.Slice(2).StartsWith("turday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 8;
                            slice = inputSpan.Slice(pos);
                            break;

                        case 'u':
                            // Match the string "nday".
                            if (!slice.Slice(2).StartsWith("nday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 6;
                            slice = inputSpan.Slice(pos);
                            break;

                        default:
                            return false; // The input didn't match.
                    }
                }

                break;

            default:
                return false; // The input didn't match.
        }
    }

    // The input matched.
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;
}

Figyelje meg, hogyan Thursday lett átrendezve, hogy csak utána Tuesdaylegyen, és hogy a pár és a TuesdaySunday/ThursdaySaturday/pár esetében is több kapcsolószinttel kell rendelkeznie. Szélsőséges esetben, ha sok különböző szó hosszú váltakozását hozná létre, a forrásgenerátor végül egy^1 trie logikai megfelelőjének kibocsátja az egyes karaktereket, és switch"az ágra ing a szó fennmaradó részének kezeléséhez. Ez egy nagyon hatékony módszer a szavak egyeztetésére, és ezt teszi itt a forrásgenerátor.

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.IgnoreCasehasonló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.dllré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:

A nem támogatott regex továbbra is gyorsítótárazva van

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, ahogy RegexOptions.Compiled szükségtelen is lehet, 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:

RegexGenerator-elemző és -javító

Lásd még