Генераторы источников регулярных выражений .NET

Регулярное выражение или регулярное выражение — это строка, которая позволяет разработчику выразить поиск шаблона, что делает его очень распространенным способом поиска текста и извлечения результатов в виде подмножества из поисковой строки. В .NET System.Text.RegularExpressions пространство имен используется для определения Regex экземпляров и статических методов и сопоставления с пользовательскими шаблонами. В этой статье вы узнаете, как использовать создание источников для создания Regex экземпляров для оптимизации производительности.

Примечание.

По возможности используйте созданные источником регулярные выражения вместо компиляции регулярных выражений RegexOptions.Compiled с помощью параметра. Создание источника может помочь вашему приложению начать быстрее, ускорить работу и быть более обрезаемым. Чтобы узнать, когда возможно создание источника, см. статью "Когда его использовать".

Скомпилированные регулярные выражения

Когда вы пишете new Regex("somepattern"), происходит несколько вещей. Указанный шаблон анализируется как для обеспечения допустимости шаблона, так и для преобразования его в внутреннее дерево, представляющее синтаксический ретекс. Затем дерево оптимизировано различными способами, преобразуя шаблон в функционально эквивалентный вариант, который может быть более эффективно выполнен. Дерево записывается в форму, которая может быть интерпретирована как ряд опкодов и операндов, которые предоставляют инструкции обработчику интерпретатора regex по сопоставлению. При выполнении совпадения интерпретатор просто проходит эти инструкции, обрабатывая их с помощью входного текста. При создании экземпляра нового Regex экземпляра или вызове одного из статических методов Regexинтерпретатор используется подсистемой по умолчанию.

При указании RegexOptions.Compiledбудет выполняться все одно и то же время строительства. Результирующие инструкции будут преобразованы компилятором на основе отражения в инструкции IL, которые будут записаны на несколько DynamicMethodсекунд. При выполнении совпадения эти DynamicMethodзначения будут вызываться. Этот IL, по сути, сделает именно то, что будет делать интерпретатор, за исключением специализированных для точной обработки шаблона. Например, если шаблон содержится [ac], интерпретатор увидит опкод, который сказал, что "соответствует входному символу в текущей позиции с заданным в этом наборе описанием", в то время как скомпилированный IL будет содержать код, который эффективно сказал: "соответствует входному символу в текущей позиции или 'a''c'". Это специальное регистрирование и способность выполнять оптимизации на основе знаний о шаблоне являются некоторыми из основных причин для указания RegexOptions.Compiled пропускной способности гораздо быстрее сопоставления пропускной способности, чем интерпретатор.

Существует несколько недостатков RegexOptions.Compiled. Наиболее значимым является то, что он несет гораздо большую стоимость строительства, чем использование интерпретатора. Не только те же затраты оплачиваются, как и для интерпретатора, но затем необходимо компилировать это результирующее RegexNode дерево и созданные opcodes/операнды в IL, что добавляет нетривиальные расходы. Созданный IL дополнительно должен быть скомпилирован JIT-код при первом использовании, что приводит к еще большему затратам при запуске. RegexOptions.Compiled представляет собой фундаментальный компромисс между затратами на первое использование и накладные расходы на каждое последующее использование. Использование System.Reflection.Emit также препятствует использованию RegexOptions.Compiled в определенных средах; некоторые операционные системы не позволяют динамически создавать код для выполнения, а в таких системах Compiled станет no-op.

Создание источника

В .NET 7 появился новый RegexGenerator генератор источника. Когда компилятор C# был перезаписан в качестве компилятора C# Roslyn, он предоставляет объектные модели для всего конвейера компиляции, а также анализаторы. В последнее время генераторы источников с поддержкой Roslyn. Как и анализатор, генератор источника является компонентом, который подключается к компилятору и передает все те же сведения, что и анализатор, но в дополнение к возможности выдавать диагностика, он также может расширить единицу компиляции с дополнительным исходным кодом. Пакет SDK для .NET 7+ включает новый генератор источника, который распознает новый GeneratedRegexAttribute в частичном методе, возвращаемом Regex. Генератор источника предоставляет реализацию этого метода, который реализует всю логику для этого Regexметода. Например, возможно, вы написали код следующим образом:

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

Теперь можно переписать предыдущий код следующим образом:

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

Созданная реализация AbcOrDefGeneratedRegex() аналогично кэширует одинтонный Regex экземпляр, поэтому для использования кода не требуется дополнительное кэширование.

Совет

Флаг RegexOptions.Compiled игнорируется генератором источника, поэтому он больше не нужен в исходной созданной версии.

На следующем рисунке показан снимок экрана исходного созданного кэшированного экземпляра в internalRegex подкласс, который генерирует генератор источника:

Кэшированное статическое поле regex

Но, как видно, это не просто делает new Regex(...). Скорее, генератор источника создает в виде кода C# пользовательскую Regexпроизводную реализацию с логикой, аналогичной тому, что RegexOptions.Compiled выдает в IL. Вы получаете все преимущества RegexOptions.Compiled производительности пропускной способности (больше, на самом деле) и преимущества Regex.CompileToAssemblyзапуска, но без сложности CompileToAssembly. Источник, который создается, является частью проекта, что означает, что он также легко просматривается и отлаживаться.

Отладка с помощью исходного кода Regex

Совет

В Visual Studio щелкните правой кнопкой мыши объявление частичного метода и выберите "Перейти к определению". Кроме того, выберите узел проекта в Обозреватель решений, а затем разверните узел анализаторов>зависимостей>System.Text.RegularExpressions.Generator>System.Text.RegularExpressions.Generator.RegexGenerator>RegexGenerator.g.cs, чтобы просмотреть созданный код C# из этого генератора regex.

Вы можете задать точки останова в нем, вы можете выполнить его, и вы можете использовать его в качестве средства обучения, чтобы точно понять, как обработчик регулярных выражений обрабатывает шаблон с помощью входных данных. Генератор даже создает комментарии с тройной косой чертой (XML), чтобы сделать выражение понятным на первый взгляд и где он используется.

Созданные XML-комментарии, описывающие regex

Внутри исходных файлов

При использовании .NET 7 исходный генератор и RegexCompiler почти полностью перезаписали, принципиально изменяя структуру созданного кода. Этот подход был расширен для обработки всех конструкций (с одним предупреждением), и RegexCompiler оба и генератор источника по-прежнему сопоставляются в основном 1:1 друг с другом, следуя новому подходу. Рассмотрим выходные данные генератора источника для одной из основных функций из (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();
        }
    }
}

Цель исходного кода заключается в том, чтобы быть понятным, с простой структурой, с комментариями, объясняющими, что делается на каждом шаге, и в целом с кодом, созданным в соответствии с руководящим принципом, что генератор должен выдавать код, как будто человек написал его. Даже при использовании обратного отслеживания структура обратного отслеживания становится частью структуры кода, а не на основе стека, чтобы указать, куда перейти дальше. Например, ниже приведен код для той же созданной функции сопоставления, если выражение :[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;
}

Структуру обратного отслеживания в коде можно увидеть с меткой, CharLoopBacktrack выдаваемой для того, чтобы вернуться к ней и goto перейти в это расположение при сбое последующей части регрессии.

Если вы посмотрите на реализацию RegexCompiler кода и генератор источника, они будут выглядеть очень похоже: аналогичные методы, аналогичные структуры вызовов и даже аналогичные комментарии во всей реализации. В большинстве случаев они приводят к тому же коду, хотя один в IL и один в C#. Конечно, компилятор C# отвечает за преобразование C# в IL, поэтому результирующий IL в обоих случаях, скорее всего, не будет идентичным. Генератор источника зависит от того, что в различных случаях используется, используя тот факт, что компилятор C# будет дополнительно оптимизировать различные конструкции C#. Существует несколько конкретных вещей, которые генератор источника, таким образом, создаст более оптимизированный код сопоставления, чем это делает RegexCompiler. Например, в одном из предыдущих примеров можно увидеть генератор источника, создающий инструкцию switch, с одной ветвью для 'a' одной ветви и другой ветви.'b' Так как компилятор C# очень хорошо оптимизирован для оптимизации инструкций коммутатора, с несколькими стратегиями в своем распоряжении для эффективного выполнения этого, генератор источника имеет специальную оптимизацию, которая RegexCompiler не выполняется. Для изменения исходный генератор смотрит на все ветви, и если это может доказать, что каждая ветвь начинается с другого начального символа, она будет выдавать оператор switch над этим первым символом и не выводить любой код обратного отслеживания для этого изменения.

Вот немного более сложный пример этого. Изменения более тщательно анализируются, чтобы определить, можно ли рефакторингировать их таким образом, чтобы сделать их более легко оптимизированными для подсистем обратного отслеживания и что приведет к более простому исходному коду. Одна из таких оптимизаций поддерживает извлечение общих префиксов из ветвей, и если изменение атомарно, что упорядочивание не имеет значения, переупорядочение ветвей, чтобы обеспечить больше такого извлечения. Вы можете увидеть влияние этого для следующего шаблона Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sundayдня недели, которое создает соответствующую функцию, как показано ниже:

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

Обратите внимание на то, как Thursday было переупорядочено сразу послеTuesday, и как для/TuesdayThursday пары и Saturday/Sunday пары, вы в конечном итоге будете иметь несколько уровней коммутаторов. В крайнем случае, если бы вы создали длинное чередование многих разных слов, генератор источника в конечном итоге создаст логический эквивалент трие^1, считывая каждый символ и switch"ing в ветвь для обработки оставшейся части слова. Это очень эффективный способ сопоставления слов, и это то, что генератор источника делает здесь.

В то же время генератор источника имеет другие проблемы, с которыми можно бороться, что просто не существует при выходе в IL напрямую. Если вы посмотрите несколько примеров кода назад, вы можете увидеть некоторые фигурные скобки несколько странно закомментированы. Это не ошибка. Генератор источника признает, что, если эти фигурные скобки не были закомментированы, структура обратного отслеживания зависит от перехода извне область на метку, определенную внутри этого область; такая метка не будет видна для такого goto и код не будет компилироваться. Таким образом, генератор источника должен избежать область таким образом. В некоторых случаях это просто закомментирует область, как было сделано здесь. В других случаях, когда это невозможно, иногда может избежать конструкций, требующих область (таких как блок с несколькими операторамиif), если это будет проблематично.

Генератор источника обрабатывает все RegexCompiler дескрипторы, за исключением одного исключения. Как и при обработке RegexOptions.IgnoreCase, реализации теперь используют таблицу регистра для создания наборов во время строительства, а IgnoreCase также сведения о сопоставлении обратной ссылки должны обращаться к этой таблице регистра. Эта таблица является внутренней System.Text.RegularExpressions.dll, и сейчас, по крайней мере, код, внешний для этой сборки (включая код, создаваемый генератором источника), не имеет доступа к нему. Это делает обработку IgnoreCase обратных ссылок проблемой в исходном генераторе, и они не поддерживаются. Это одна конструкция, которая не поддерживается источником генератора, который поддерживается RegexCompiler. Если вы попытаетесь использовать шаблон с одним из этих (редких), генератор источника не будет выдавать пользовательскую реализацию и вместо этого будет возвращаться к кэшированию регулярного Regex экземпляра:

Неподдерживаемые regex по-прежнему кэшируются

Кроме того, ни RegexCompiler генератор источника не поддерживает новый RegexOptions.NonBacktracking. Если указать RegexOptions.Compiled | RegexOptions.NonBacktracking, Compiled флаг просто будет игнорироваться, и если вы указываете NonBacktracking источнику генератора, он будет аналогичным образом возвращаться к кэшированию регулярного Regex экземпляра.

Сценарии использования

Общее руководство заключается в том, если вы можете использовать генератор источника, используйте его. Если вы используете сегодня в C# с аргументами, известными во время компиляции, и особенно если вы уже используете RegexRegexOptions.Compiled (так как ретекс был определен как горячая точка, которая будет пользоваться более быстрой пропускной способностью), следует использовать генератор источника. Генератор источника дает следующие преимущества:

  • Все преимущества пропускной способности RegexOptions.Compiled.
  • Преимущества запуска не требуется выполнять все синтаксический анализ, анализ и компиляцию во время выполнения.
  • Возможность использования предварительной компиляции с кодом, созданным для регулярного выражения.
  • Улучшена отладка и понимание регрессии.
  • Возможность уменьшить размер обрезаемого приложения путем обрезки больших полос кода, связанных с RegexCompiler (и потенциально даже отражение выдает себя).

При использовании с параметром, например RegexOptions.NonBacktracking , для которого генератор источника не может создать пользовательскую реализацию, он по-прежнему выдает кэширование и XML-комментарии, описывающие реализацию, что делает его ценным. Основной недостатком исходного генератора является то, что он выдает дополнительный код в сборку, поэтому существует потенциал для увеличения размера. Чем больше регексов в приложении, так и чем больше, тем больше код будет выдаваться для них. В некоторых ситуациях так же, как RegexOptions.Compiled и может быть ненужным, поэтому может быть источником генератора. Например, если у вас есть regex, который требуется только редко и для какой пропускной способности не имеет значения, это может быть более полезно просто полагаться на интерпретатор для этого спорадического использования.

Внимание

.NET 7 включает анализатор, определяющий использованиеRegex, которое может быть преобразовано в исходный генератор, и средство исправления, которое выполняет преобразование для вас:

Анализатор и исправление RegexGenerator

См. также