Delen via


.NET-brongeneratoren voor reguliere expressies

Een reguliere expressie, of regex, is een tekenreeks waarmee een ontwikkelaar een patroon kan uitdrukken waarnaar wordt gezocht, waardoor het een zeer algemene manier is om tekst te zoeken en resultaten te extraheren als een subset uit de gezochte tekenreeks. In .NET wordt de naamruimte gebruikt om instanties en statische methoden te definiëren Regex en overeen te komen met door de System.Text.RegularExpressions gebruiker gedefinieerde patronen. In dit artikel leert u hoe u brongeneratie kunt gebruiken om exemplaren te genereren Regex om de prestaties te optimaliseren.

Notitie

Gebruik waar mogelijk bron gegenereerde reguliere expressies in plaats van reguliere expressies te compileren met behulp van de RegexOptions.Compiled optie. Het genereren van bronnen kan ervoor zorgen dat uw app sneller kan worden gestart, sneller kan worden uitgevoerd en beter kan worden ingekort. Als u wilt weten wanneer brongeneratie mogelijk is, raadpleegt u Wanneer u deze gebruikt.

Gecompileerde reguliere expressies

Wanneer u schrijft new Regex("somepattern"), gebeuren er een paar dingen. Het opgegeven patroon wordt geparseerd, zowel om de geldigheid van het patroon te garanderen als om het te transformeren in een interne structuur die de geparseerde regex vertegenwoordigt. De structuur wordt vervolgens op verschillende manieren geoptimaliseerd, waardoor het patroon wordt omgezet in een functioneel equivalente variatie die efficiënter kan worden uitgevoerd. De structuur wordt geschreven in een formulier dat kan worden geïnterpreteerd als een reeks opcodes en operanden die instructies bieden aan de regex-interpreter-engine voor de overeenkomst. Wanneer een overeenkomst wordt uitgevoerd, doorloopt de interpreter deze instructies, zodat deze worden verwerkt op basis van de invoertekst. Bij het instantiëren van een nieuw Regex exemplaar of het aanroepen van een van de statische methoden Regexop, is de interpreter de standaardengine die wordt gebruikt.

Wanneer u opgeeft RegexOptions.Compiled, worden alle bouwtijdwerkzaamheden uitgevoerd. De resulterende instructies worden verder getransformeerd door de compiler op basis van reflectie-emit in IL-instructies die naar een paar DynamicMethods worden geschreven. Wanneer een overeenkomst is uitgevoerd, worden deze DynamicMethods aangeroepen. Deze IL zou in wezen precies doen wat de interpreter zou doen, behalve gespecialiseerd voor het exacte patroon dat wordt verwerkt. Als het patroon bijvoorbeeld is opgenomen [ac], ziet de interpreter een opcode met de tekst 'match the input character at the current position against the set specified in this set description', terwijl de gecompileerde IL code bevat die effectief zei: "match the input character at the current position against 'a' or 'c'". Deze speciale behuizing en de mogelijkheid om optimalisaties uit te voeren op basis van kennis van het patroon zijn enkele van de belangrijkste redenen voor het opgeven RegexOptions.Compiled van een veel snellere overeenkomende doorvoer dan de interpreter.

Er zijn verschillende nadelen aan RegexOptions.Compiled. Het meest impactvol is dat er veel meer bouwkosten in rekening worden gebracht dan het gebruik van de interpreter. Niet alleen zijn alle kosten die voor de interpreter worden betaald, maar vervolgens moet deze resulterende RegexNode structuur worden gecompileerd en gegenereerde opcodes/operanden in IL, waardoor niet-triviale kosten worden toegevoegd. De gegenereerde IL moet verder worden gecompileerd op basis van het eerste gebruik, wat leidt tot nog meer kosten bij het opstarten. RegexOptions.Compiled vertegenwoordigt een fundamentele afweging tussen overhead voor het eerste gebruik en de overhead voor elk volgend gebruik. Het gebruik van System.Reflection.Emit remt ook het gebruik van RegexOptions.Compiled in bepaalde omgevingen; sommige besturingssystemen staan niet toe dat dynamisch gegenereerde code wordt uitgevoerd, en op dergelijke systemen Compiled wordt een no-op.

Brongeneratie

.NET 7 heeft een nieuwe RegexGenerator brongenerator geïntroduceerd. Toen de C#-compiler werd herschreven als de C#-compiler 'Roslyn', werden objectmodellen voor de gehele compilatiepijplijn en analysen weergegeven. Recenter ingeschakelde brongeneratoren met Roslyn. Net als bij een analyse is een brongenerator een onderdeel dat wordt aangesloten op de compiler en alle informatie als een analyse wordt gegeven, maar naast het kunnen verzenden van diagnostische gegevens, kan het ook de compilatie-eenheid uitbreiden met aanvullende broncode. De .NET 7+ SDK bevat een nieuwe brongenerator die de nieuwe GeneratedRegexAttribute herkent op een gedeeltelijke methode die retourneert Regex. De brongenerator biedt een implementatie van die methode waarmee alle logica voor de Regex. U hebt bijvoorbeeld code als volgt geschreven:

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

U kunt de vorige code nu als volgt herschrijven:

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

De gegenereerde implementatie van AbcOrDefGeneratedRegex() een singleton-exemplaar slaat een singleton-exemplaar Regex op, dus er is geen extra caching nodig om code te gebruiken.

Tip

De RegexOptions.Compiled vlag wordt genegeerd door de brongenerator, waardoor deze niet meer nodig is in de door de bron gegenereerde versie.

De volgende afbeelding is een schermopname van het bron gegenereerde exemplaar in de cache voor internal de Regex subklasse die door de brongenerator wordt verzonden:

Statisch veld regex in cache

Maar zoals te zien is, is het niet alleen aan het doen new Regex(...). In plaats daarvan verzendt de brongenerator als C#-code een aangepaste Regex- afgeleide implementatie met logica die vergelijkbaar is met wat RegexOptions.Compiled in IL wordt verzonden. U krijgt alle voordelen van RegexOptions.Compiled doorvoerprestaties van (meer, in feite) en de opstartvoordelen van Regex.CompileToAssembly, maar zonder de complexiteit van CompileToAssembly. De bron die wordt verzonden, maakt deel uit van uw project, wat betekent dat het ook eenvoudig kan worden weergegeven en foutopsporing kan worden uitgevoerd.

Foutopsporing via door de bron gegenereerde Regex-code

Tip

Klik in Visual Studio met de rechtermuisknop op uw gedeeltelijke methodedeclaratie en selecteer Ga naar definitie. Of selecteer het projectknooppunt in Solution Explorer en vouw vervolgens Dependencies>Analyzers>System.Text.RegularExpressions.Generator>System.Text.RegularExpressions.Generator.RegexGenerator>RegexGenerator.g.cs uit om de gegenereerde C#-code van deze regex-generator te zien.

U kunt onderbrekingspunten instellen, u kunt er stapsgewijs doorheen en u kunt het gebruiken als leerhulpmiddel om precies te begrijpen hoe de regex-engine uw patroon verwerkt met uw invoer. De generator genereert zelfs xml-opmerkingen (triple-slash) om de expressie begrijpelijk te maken en waar deze wordt gebruikt.

Gegenereerde XML-opmerkingen met een beschrijving van regex

Binnen de door de bron gegenereerde bestanden

Met .NET 7 werden zowel de brongenerator als RegexCompiler bijna volledig herschreven, waarbij de structuur van de gegenereerde code fundamenteel werd gewijzigd. Deze benadering is uitgebreid om alle constructies te verwerken (met één kanttekening) en zowel RegexCompiler als de brongenerator wijst nog steeds voornamelijk 1:1 met elkaar toe, na de nieuwe benadering. Bekijk de uitvoer van de brongenerator voor een van de primaire functies uit de (a|bc)d expressie:

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

Het doel van de door de broncode gegenereerde code is begrijpelijk te zijn, met een eenvoudig te volgen structuur, met opmerkingen waarin wordt uitgelegd wat er in elke stap wordt gedaan, en in het algemeen met code die wordt verzonden volgens het leidende principe dat de generator code moet verzenden alsof een mens deze had geschreven. Zelfs wanneer backtracking is betrokken, wordt de structuur van de backtracking onderdeel van de structuur van de code, in plaats van te vertrouwen op een stack om aan te geven waar u naartoe moet springen. Hier ziet u bijvoorbeeld de code voor dezelfde gegenereerde overeenkomende functie als de expressie:[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;
}

U kunt de structuur van de backtracking in de code zien, met een CharLoopBacktrack label dat wordt verzonden om terug te gaan naar en een goto gebruikt om naar die locatie te gaan wanneer een volgend gedeelte van de regex mislukt.

Als u de code implementeert RegexCompiler en de brongenerator bekijkt, zien ze er zeer vergelijkbaar uit: vergelijkbare methoden, vergelijkbare aanroepstructuur en zelfs vergelijkbare opmerkingen tijdens de implementatie. Voor het grootste deel resulteren ze in identieke code, maar wel één in IL en één in C#. Natuurlijk is de C#-compiler vervolgens verantwoordelijk voor het vertalen van de C# in IL, dus de resulterende IL in beide gevallen is waarschijnlijk niet identiek. De brongenerator is afhankelijk van dat in verschillende gevallen, waarbij gebruik wordt gemaakt van het feit dat de C#-compiler verschillende C#-constructies verder zal optimaliseren. Er zijn enkele specifieke dingen die de brongenerator dus meer geoptimaliseerde overeenkomende code produceert dan wel RegexCompiler. In een van de vorige voorbeelden ziet u bijvoorbeeld dat de brongenerator een switchinstructie verzendt, met één vertakking voor 'a' en een andere vertakking voor 'b'. Omdat de C#-compiler zeer goed is bij het optimaliseren van switch-instructies, met meerdere strategieën die beschikbaar zijn om dit efficiënt te doen, heeft de brongenerator een speciale optimalisatie die RegexCompiler dat niet doet. Voor alternaties kijkt de brongenerator naar alle vertakkingen en als het kan bewijzen dat elke vertakking begint met een ander beginteken, wordt er een switch-instructie over dat eerste teken verzonden en wordt voorkomen dat er backtrackingcode voor die alternatie wordt uitgevoerd.

Hier volgt een iets gecompliceerder voorbeeld hiervan. Alternations worden zwaarder geanalyseerd om te bepalen of het mogelijk is om ze te herstructureren op een manier die ze gemakkelijker zal optimaliseren door de backtracking-engines en dat zal leiden tot eenvoudigere broncode. Een dergelijke optimalisatie ondersteunt het extraheren van veelvoorkomende voorvoegsels uit vertakkingen en als de alternatie atomisch is, zodat volgorde niet uitmaakt, kunt u vertakkingen opnieuw ordenen om meer dergelijke extractie mogelijk te maken. U kunt de impact hiervan voor het volgende weekdagpatroon Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sundayzien, waardoor een overeenkomende functie als volgt wordt geproduceerd:

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

Let op hoe Thursday de volgorde is aangepast om net na Tuesdayte zijn en hoe voor zowel het Tuesday/Thursday paar als het Saturday/Sunday paar, u uiteindelijk met meerdere niveaus van schakelaars komt. Als u een lange alternatie van veel verschillende woorden zou maken, zou de brongenerator uiteindelijk het logische equivalent van een trie^1 genereren, waarbij elk teken en switch'ing naar de vertakking worden gelezen voor het verwerken van de rest van het woord. Dit is een zeer efficiënte manier om woorden te vinden en het is wat de brongenerator hier doet.

Tegelijkertijd heeft de brongenerator andere problemen om mee te maken dat er simpelweg niet bestaat bij het rechtstreeks uitvoeren naar IL. Als u een aantal codevoorbeelden terugkijkt, ziet u enkele accolades die enigszins vreemd zijn gecommentarieerd. Dat is geen vergissing. De brongenerator erkent dat, als deze accolades niet zijn gemarkeerd, de structuur van de backtracking afhankelijk is van buiten het bereik naar een label dat binnen dat bereik is gedefinieerd; een dergelijk label zou niet zichtbaar zijn voor een goto dergelijke en de code zou niet worden gecompileerd. De brongenerator moet dus voorkomen dat er een bereik op de manier is. In sommige gevallen markeert het gewoon het bereik zoals hier is gedaan. In andere gevallen waarin dat niet mogelijk is, kan het soms voorkomen dat constructies waarvoor bereiken zijn vereist (zoals een blok met meerdere if instructies) als dit problematisch zou zijn.

De brongenerator verwerkt alles RegexCompiler , met één uitzondering. Net als bij de verwerking RegexOptions.IgnoreCasegebruiken de implementaties nu een behuizingstabel om sets tijdens de bouw te genereren en hoe IgnoreCase backreference-overeenkomsten die moeten worden gebruikt om die behuizingstabel te raadplegen. Deze tabel is intern voor System.Text.RegularExpressions.dll, en voorlopig heeft de code buiten die assembly (inclusief code die wordt verzonden door de brongenerator) geen toegang. Dit maakt het verwerken IgnoreCase van backreferences een uitdaging in de brongenerator en ze worden niet ondersteund. Dit is de enige constructie die niet wordt ondersteund door de brongenerator die wordt ondersteund door RegexCompiler. Als u probeert een patroon te gebruiken dat een van deze (zeldzaam) heeft, zal de brongenerator geen aangepaste implementatie verzenden en in plaats daarvan terugvallen op het opslaan van een normaal Regex exemplaar:

Niet-ondersteunde regex wordt nog steeds in de cache opgeslagen

Ook ondersteunt noch RegexCompiler noch de brongenerator de nieuwe RegexOptions.NonBacktracking. Als u opgeeft, wordt de Compiled vlag gewoon genegeerd en als u opgeeft RegexOptions.Compiled | RegexOptions.NonBacktrackingNonBacktracking aan de brongenerator, zal deze op dezelfde manier terugvallen op het opslaan van een normaal Regex exemplaar.

Wanneer te gebruiken

De algemene richtlijnen zijn als u de brongenerator kunt gebruiken. Als u Regex vandaag in C# gebruikt met argumenten die bekend zijn tijdens het compileren, en vooral als u al gebruikt RegexOptions.Compiled (omdat de regex is geïdentificeerd als een hot spot die zou profiteren van snellere doorvoer), moet u de brongenerator liever gebruiken. De brongenerator biedt uw regex de volgende voordelen:

  • Alle doorvoervoordelen van RegexOptions.Compiled.
  • De opstartvoordelen van het niet hoeven uitvoeren van alle regex parsering, analyse en compilatie tijdens runtime.
  • De optie om vooraf compilatie te gebruiken met de code die voor de regex is gegenereerd.
  • Betere foutopsporing en inzicht in de regex.
  • De mogelijkheid om de grootte van uw bijgesneden app te verkleinen door grote stukjes code uit te snijden die zijn RegexCompiler gekoppeld aan (en mogelijk zelfs weerspiegeling verzendt zichzelf).

Wanneer deze wordt gebruikt met een optie zoals RegexOptions.NonBacktracking waarvoor de brongenerator geen aangepaste implementatie kan genereren, worden er nog steeds caching- en XML-opmerkingen verzonden waarin de implementatie wordt beschreven, waardoor deze waardevol is. Het belangrijkste nadeel van de brongenerator is dat er extra code in uw assembly wordt verzonden, dus er is het potentieel voor grotere grootte. Hoe meer regexes in uw app en hoe groter ze zijn, hoe meer code er voor wordt verzonden. In sommige situaties, net zoals RegexOptions.Compiled onnodig, kan dit ook de brongenerator zijn. Als u bijvoorbeeld een regex hebt die slechts zelden nodig is en waarvoor doorvoer niet van belang is, kan het nuttiger zijn om alleen te vertrouwen op de interpreter voor dat sporadische gebruik.

Belangrijk

.NET 7 bevat een analyse waarmee het gebruik ervan Regex kan worden geconverteerd naar de brongenerator en een fixer die de conversie voor u doet:

RegexGenerator analyzer en fixer

Zie ook