Generátory zdrojů regulárních výrazů .NET

Regulární výraz neboli regulární výraz je řetězec, který vývojářům umožňuje vyjádřit hledaný vzor, což z něj dělá velmi běžný způsob, jak hledat text a extrahovat výsledky jako podmnožinu z hledaného řetězce. V .NET se System.Text.RegularExpressions obor názvů používá k definování Regex instancí a statických metod a porovnávání s uživatelsky definovanými vzory. V tomto článku se dozvíte, jak pomocí generování zdrojového kódu generovat Regex instance pro optimalizaci výkonu.

Poznámka:

Pokud je to možné, místo kompilování regulárních výrazů pomocí RegexOptions.Compiled možnosti použijte zdrojové vygenerované regulární výrazy. Generování zdrojového kódu může vaší aplikaci pomoct rychleji, rychleji spustit a být lépe oříznutelná. Informace o tom, kdy je možné generování zdroje, najdete v tématu Kdy ji použít.

Kompilované regulární výrazy

Když píšete new Regex("somepattern"), stane se pár věcí. Zadaný vzor se analyzuje, a to jak k zajištění platnosti vzoru, tak k jeho transformaci na interní strom, který představuje parsovaný regulární výraz. Strom se pak optimalizuje různými způsoby a transformuje model na funkčně ekvivalentní variantu, která se dá efektivněji spustit. Strom se zapíše do formuláře, který lze interpretovat jako řadu opcode a operandů, které poskytují pokyny interpretačnímu stroji regulárních výrazů, jak se shodovat. Když se provede shoda, interpret je jednoduše provede těmito pokyny a zpracuje je proti vstupnímu textu. Při vytváření instance nové Regex instance nebo volání jedné ze statických metod Regexje interpret použit výchozí modul.

Pokud zadáte RegexOptions.Compiled, budou provedeny všechny stejné stavební práce. Výsledné instrukce by byly dále transformovány kompilátorem založeným na reflexi na generování do instrukcí IL, které by byly zapsány na několik DynamicMethods. Při provedení shody by se tato DynamicMethodshoda vyvolala. Tento il by v podstatě udělal přesně to, co by interpret udělal, s výjimkou specializovaného na přesný vzor, který se zpracovává. Pokud by například vzorek obsahoval [ac], interpret by viděl opcode, který říká " odpovídá vstupnímu znaku na aktuální pozici proti sadě zadané v popisu této sady", zatímco zkompilovaná il by obsahovala kód, který efektivně řekl, "odpovídá vstupnímu znaku na aktuální pozici 'a' proti nebo 'c'". Tato speciální velikost a schopnost provádět optimalizace na základě znalostí modelu jsou některými hlavními důvody pro určení RegexOptions.Compiled mnohem rychlejší a odpovídající propustnosti, než interpret.

Existuje několik nevýhod RegexOptions.Compiled. Nejvýraznější je, že za vás stojí mnohem více stavebních nákladů než používání interpreta. Nejen, že jsou všechny stejné náklady zaplacené za interpreta, ale pak musí zkompilovat výsledný RegexNode strom a vygenerované opcodes/operandy do IL, což přidává ne-triviální výdaje. Vygenerované IL musí být dále zkompilovány JIT při prvním použití, což vede k ještě větším nákladům při spuštění. RegexOptions.Compiled představuje základní kompromis mezi režií při prvním použití a režijními náklady při každém následném použití. Použití System.Reflection.Emit také inhibuje použití RegexOptions.Compiled v určitých prostředích; některé operační systémy neumožňují dynamicky vygenerovaný kód spustit a v takových systémech Compiled se stane no-op.

Generování zdroje

.NET 7 zavedl nový RegexGenerator generátor zdrojů. Když se kompilátor jazyka C# přepsal jako kompilátor jazyka C# Roslyn, odhalil objektové modely pro celý kanál kompilace a také analyzátory. V poslední době aktivují generátory zdrojů Roslyn. Stejně jako analyzátor je zdrojový generátor komponentou, která se připojí k kompilátoru a předává všechny stejné informace jako analyzátor, ale kromě toho, že dokáže generovat diagnostiku, může také rozšířit kompilační jednotku o další zdrojový kód. Sada .NET 7+ SDK obsahuje nový generátor zdroje, který rozpozná nové GeneratedRegexAttribute u částečné metody, která vrací Regex. Generátor zdroje poskytuje implementaci této metody, která implementuje veškerou logiku Regexpro . Můžete mít například napsaný kód podobný tomuto:

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

Předchozí kód teď můžete přepsat následujícím způsobem:

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

Vygenerovaná implementace AbcOrDefGeneratedRegex() podobně ukládá jednu Regex instanci do mezipaměti, takže ke zpracování kódu není potřeba žádné další ukládání do mezipaměti.

Tip

Příznak RegexOptions.Compiled je ignorován generátorem zdroje, takže už ho nepotřebujete ve zdrojové vygenerované verzi.

Následující obrázek je snímek obrazovky zdrojové instance internal vygenerované v mezipaměti do Regex podtřídy, kterou generuje generátor zdrojů:

Statické pole regulárních výrazů uložených v mezipaměti

Ale jak je vidět, není to jen dělá new Regex(...). Zdrojový generátor spíše generuje jako kód jazyka C# vlastní Regeximplementaci odvozenou odvozující logiku podobnou tomu, co RegexOptions.Compiled generuje v IL. Získáte všechny výhody RegexOptions.Compiled výkonu propustnosti (ve skutečnosti) a výhody Regex.CompileToAssemblyspuštění , ale bez složitosti CompileToAssembly. Zdroj, který se vygeneruje, je součástí projektu, což znamená, že je také snadno zobrazitelný a laditelný.

Ladění prostřednictvím zdrojového kódu Regex

Tip

V sadě Visual Studio klikněte pravým tlačítkem na deklaraci částečné metody a vyberte Přejít k definici. Nebo případně vyberte uzel projektu v Průzkumník řešení a pak rozbalte závislosti>Analyzers>System.Text.RegularExpressions.Generator>System.Text.RegularExpressions.Generator.RegexGenerator>RegexGenerator.g.cs zobrazit vygenerovaný kód C# z tohoto generátoru regulárních výrazů.

V něm můžete nastavit zarážky, můžete je procházet a můžete ho použít jako výukový nástroj, abyste přesně pochopili, jak modul regulárních výrazů zpracovává váš vzor se vstupem. Generátor dokonce generuje komentáře trojité lomítko (XML), aby byl výraz srozumitelný na první pohled a tam, kde se používá.

Generované komentáře XML popisující regex

Uvnitř zdrojových souborů

S rozhraním .NET 7 se zdrojový generátor i RegexCompiler téměř zcela přepsal, v podstatě se změnila struktura generovaného kódu. Tento přístup byl rozšířen tak, aby zpracovával všechny konstrukce (s jedním upozorněním) a RegexCompiler zdrojový generátor se stále mapuje většinou 1:1 s ostatními, a to po novém přístupu. Představte si výstup generátoru zdroje pro jednu z primárních funkcí z výrazu (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();
        }
    }
}

Cílem zdrojového vygenerovaného kódu je pochopitelný, s snadno sledovatelnou strukturou, s komentáři vysvětlující, co se provádí v jednotlivých krocích, a obecně s kódem vygenerovaným v rámci hlavního principu, že generátor by měl generovat kód, jako by ho napsal člověk. I když je zpětné navracení zapojeno, struktura zpětného navracení se stane součástí struktury kódu, místo aby se spoléhala na zásobník, aby bylo zřejmé, kam se má přejít. Tady je například kód pro stejnou vygenerovanou odpovídající funkci, když je [ab]*[bc]výraz:

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

Můžete vidět strukturu zpětného navracení v kódu s CharLoopBacktrack popiskem, který se vygeneruje pro umístění zpětného navracení a goto použitý k přeskočení na toto místo, když další část regulárního výrazu selže.

Pokud se podíváte na implementaci RegexCompiler kódu a generátor zdrojového kódu, budou vypadat velmi podobně: podobně pojmenované metody, podobná struktura volání a dokonce podobné komentáře v celé implementaci. Ve většině případů mají za následek stejný kód, i když jeden v IL a druhý v jazyce C#. Kompilátor jazyka C# je samozřejmě zodpovědný za překlad jazyka C# do IL, takže výsledný il v obou případech pravděpodobně nebude identický. Zdrojový generátor spoléhá na to v různých případech, přičemž využívá skutečnost, že kompilátor jazyka C# dále optimalizuje různé konstrukce jazyka C#. Existuje několik konkrétních věcí, které zdrojový generátor vytvoří optimalizovanější odpovídající kód, než dělá RegexCompiler. Například v jednom z předchozích příkladů můžete zobrazit zdrojový generátor vygenerující příkaz switch s jednou větví pro 'a' a jinou větví pro 'b'. Vzhledem k tomu, že kompilátor jazyka C# je velmi dobrý při optimalizaci příkazů přepínače, s několika strategiemi, které jsou k dispozici pro efektivní způsob, má generátor zdrojů speciální optimalizaci, která RegexCompiler ne. V případě alternací se zdrojový generátor podívá na všechny větve a pokud může prokázat, že každá větev začíná jiným počátečním znakem, vygeneruje příkaz switch přes tento první znak a vyhne se výstupu jakéhokoli kódu zpětného navracení tohoto alternace.

Tady je trochu složitější příklad. Alternace jsou silně analyzovány, aby se zjistilo, jestli je možné refaktorovat způsobem, který je snadněji optimalizuje moduly pro navracení a které vedou k jednoduššímu zdrojovému kódu. Jedna z těchto optimalizací podporuje extrahování běžných předpon z větví a pokud je alternace atomická tak, aby řazení nezáleželo, přeuspořádejte větve tak, aby umožňovaly další takovou extrakci. Můžete vidět dopad tohoto vzorce Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sundaypro následující pracovní den , který vytváří odpovídající funkci takto:

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

Všimněte si, jak Thursday bylo přeuspořádané tak, aby bylo hned po Tuesday, a jak pro/TuesdayThursday dvojici i Saturday/Sunday dvojici, skončíte s několika úrovněmi přepínačů. V extrémním případě, kdybyste chtěli vytvořit dlouhé alternace mnoha různých slov, generátor zdroje by nakonec vygeneroval logický ekvivalent trie^1, čtení každého znaku a switch'ing do větve pro zpracování zbytku slova. Jedná se o velmi efektivní způsob, jak spárovat slova a je to, co tady dělá generátor zdrojů.

Ve stejnou dobu má zdrojový generátor další problémy, které se musí potýkat s tím, že při přímém výstupu do IL prostě neexistují. Pokud se podíváte na několik příkladů kódu zpět, můžete vidět některé složené závorky poněkud podivně okomentované. To není chyba. Generátor zdrojů rozpozná, že pokud tyto složené závorky nebyly okomentovány, struktura zpětného navracení se spoléhá na přeskakování mimo obor na popisek definovaný uvnitř tohoto oboru; takový popisek by takový popisek nebyl viditelný goto a kód by se nepodařilo zkompilovat. Zdrojový generátor se proto musí vyhnout rozsahu v cestě. V některých případech jednoduše zakomentuje rozsah, jak jsme tady udělali. V jiných případech, kdy to není možné, se někdy může vyhnout konstruktorům, které vyžadují obory (například blok s více příkazy if ), pokud by to bylo problematické.

Zdrojový generátor zpracovává všechno RegexCompiler , s jednou výjimkou. Stejně jako v případě zpracování RegexOptions.IgnoreCaseteď implementace používají k vygenerování sad v době výstavby tabulku s velikostí a způsob, jakým IgnoreCase porovnávání zpětného odvozování potřebuje prohlédnout tabulku s velikostí a velikostí. Tato tabulka je interní System.Text.RegularExpressions.dllpro toto sestavení (včetně kódu generovaného generátorem zdroje) a prozatím k němu nemá přístup aspoň kód, který je externí pro toto sestavení (včetně kódu generovaného generátorem zdroje). Díky tomu zpracování IgnoreCase backreference představuje problém ve zdrojovém generátoru a nejsou podporované. Jedná se o jeden konstruktor, který není podporován zdrojovým generátorem RegexCompiler, který je podporován . Pokud se pokusíte použít vzor, který má jednu z těchto (což je vzácné), generátor zdroje nevygeneruje vlastní implementaci a místo toho se vrátí do mezipaměti pravidelné Regex instance:

Nepodporované regulární výrazy se stále ukládají do mezipaměti

Také ani RegexCompiler generátor zdroje nepodporuje nový RegexOptions.NonBacktracking. Pokud zadáte RegexOptions.Compiled | RegexOptions.NonBacktracking, Compiled příznak se bude ignorovat a pokud zadáte NonBacktracking do zdrojového generátoru, bude se podobně vracet do mezipaměti běžné Regex instance.

Kdy ji použít

Obecné pokyny jsou, pokud můžete použít generátor zdrojů, použijte ho. Pokud používáte Regex dnes v jazyce C# s argumenty známými v době kompilace, a to zejména v případě, že už používáte RegexOptions.Compiled (protože regulární výraz byl identifikován jako aktivní bod, který by měl prospěch z rychlejší propustnosti), měli byste raději použít generátor zdrojů. Generátor zdrojů poskytne regulárnímu výrazu následující výhody:

  • Všechny výhody propustnosti .RegexOptions.Compiled
  • Výhody spuštění, které nemusí provádět všechny analýzy, analýzy a kompilace regulárních výrazů za běhu.
  • Možnost použít předem připravenou kompilaci s kódem vygenerovaným pro regulární výraz.
  • Lepší ladění a porozumění regulárnímu výrazu.
  • Možnost zmenšit velikost oříznuté aplikace oříznutím velkého množství kódu spojeného s RegexCompiler kódem (a potenciálně i reflexe se vygeneruje).

Při použití s možností, jako RegexOptions.NonBacktracking je například zdroj generátor nemůže vygenerovat vlastní implementaci, bude stále generovat ukládání do mezipaměti a komentáře XML popisující implementaci, což z něj dělá cenné. Hlavní nevýhodou zdrojového generátoru je, že do sestavení generuje další kód, takže existuje potenciál pro zvýšení velikosti. Čím více regulárních výrazů ve vaší aplikaci a čím větší jsou, tím více kódu se pro ně vygeneruje. V některých situacích, stejně jako RegexOptions.Compiled může být zbytečné, takže také může být zdrojem generátoru. Pokud máte například regulární výraz, který je potřeba jen zřídka a pro kterou propustnost nezáleží, může být vhodnější jen spoléhat na interpreta pro toto sporadické použití.

Důležité

.NET 7 obsahuje analyzátor , který identifikuje použití Regex , které by bylo možné převést na generátor zdroje, a opravovač, který provede převod za vás:

Analyzátor regexGenerator a fixer

Viz také