Поделиться через


Подробные сведения о поведении регулярных выражений

Механизм сопоставления регулярных выражений .NET выполняет сопоставление с возвратом и представляет собой реализацию традиционного недетерминированного конечного автомата (NFA), как в Perl, Python, Emacs и Tcl. Это отличает его от более быстрых, но более ограниченных машин DFA (детерминированный конечный автомат), основанных на чистых регулярных выражениях и используемых в таких инструментах, как awk, egrep или lex. Это также отличает его от стандартизованных, но более медленных POSIX недетерминированных конечных автоматов (NFA). В следующем разделе представлено описание трех типов обработчиков регулярных выражений и объясняется причина реализации регулярных выражений в .NET с помощью обычной NFA-машины.

Преимущества NFA-движка

Когда DFA-машины выполняют сопоставление шаблонов, порядок обработки определяется входной строкой. Машина начинает обработку с начала входной строки и продолжает последовательную обработку для определения соответствия следующего символа шаблону регулярного выражения. Они могут гарантировать соответствие самой длинной возможной строке. Поскольку DFA-машины не проверяют один и тот же символ дважды, они не поддерживают бэктрекинг. Однако, поскольку двигатель DFA содержит только конечное состояние, он не способен выполнять поиск соответствий по шаблону с обратными ссылками, и поскольку он не выполняет явного развертывания, он не способен выделять подвыражения.

В отличие от DFA-машин обычные NFA-машины выполняют поиск соответствий по шаблонам, и порядок обработки определяется шаблоном регулярных выражений. По мере обработки определенного элемента языка машина использует жадное сопоставление: она выполняет сопоставление как можно большей части входной строки. И кроме того, она сохраняет свое состояние после успешного поиска соответствия части выражения. Если попытка найти соответствие в конечном счёте завершается неудачей, движок может вернуться в сохранённое состояние, чтобы попробовать дополнительные соответствия. Такой процесс, при котором успешно найденное соответствие части выражения "откладывается" для поиска соответствий с последующими языковыми элементами в регулярном выражении, называется поиском с возвратом. NFA-машины используют поиск с возвратом, проверяя все возможные расширения регулярного выражения в определенном порядке и принимая первое соответствие. Поскольку обычная NFA-машина создает определенное расширение регулярного выражения для успешного сопоставления, она способна находить соответствия для частей выражений и обратных ссылок. Но так как обычная NFA-машина выполняет поиск с возвратом, она может анализировать одно и то же состояние несколько раз, если к нему ведут несколько разных путей. В результате в наихудшем случае процесс может работать экспоненциально медленно. Поскольку традиционный движок NFA принимает первое найденное соответствие, другие (возможно, более длинные) соответствия могут остаться нераспознанными.

POSIX NFA-машины похожи на обычные NFA-машины, за исключением того, что они продолжают поиск с возвратом до тех пор, пока не будет найдено наиболее длинное совпадение. В результате POSIX NFA-автомат работает медленнее обычного NFA-автомата и при использовании POSIX NFA-автомата невозможно отдать предпочтение более короткому совпадению перед более длинным, изменяя порядок поиска с возвратом.

Программисты предпочитают обычные NFA-машины, поскольку они превосходят по возможностям управления строковыми соответствиями обычные DFA-машины или POSIX NFA-машины. Несмотря на то, что в наихудшем случае они могут работать медленно, вы можете направлять их так, чтобы найти совпадения за линейное или полиномиальное время, используя шаблоны, которые уменьшают неоднозначности и ограничивают количество откатов. Другими словами, хотя NFA-машины характеризуются мощностью и гибкостью за счет производительности, в большинстве случаев они обеспечивают хорошую или приемлемую производительность, если регулярное выражение грамотно составлено и позволяет избежать ситуаций, при которых производительность из-за возврата снижается в экспоненциальной степени.

Примечание.

Сведения о влиянии чрезмерного бэктрекинга на производительность и о способах создания регулярных выражений для его обхода см. в разделе Бэктрекинг.

Возможности обработчика .NET

Чтобы воспользоваться преимуществами традиционной NFA-машины, в обработчик регулярных выражений .NET включён полный набор конструкций, позволяющий программистам управлять механизмом поиска с возвратом. Эти структуры можно использовать для ускорения поиска или для выбора предпочтительных расширений.

Ниже представлены другие возможности обработчика регулярных выражений .NET.

  • Ленивые квантификаторы: ??, *?, +?, {n,m}?. Эти конструкции указывают на необходимость поиска минимального числа повторений в первую очередь. Обычные "жадные" квантификаторы, наоборот, пытаются сначала найти наибольшее число повторений. В следующем примере демонстрируется различие между двумя. Регулярное выражение соответствует предложению, оканчивающемуся на число, и группа захвата предназначена для извлечения этого числа. Регулярное выражение .+(\d+)\. содержит жадный квантификатор .+, под влиянием которого обработчик регулярных выражений захватывает только последнюю цифру числа. И наоборот, регулярное выражение .+?(\d+)\. содержит ленивый квантификатор .+?, под влиянием которого обработчик регулярных выражений захватывает все число.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string greedyPattern = @".+(\d+)\.";
            string lazyPattern = @".+?(\d+)\.";
            string input = "This sentence ends with the number 107325.";
            Match match;
    
            // Match using greedy quantifier .+.
            match = Regex.Match(input, greedyPattern);
            if (match.Success)
                Console.WriteLine($"Number at end of sentence (greedy): {match.Groups[1].Value}");
            else
                Console.WriteLine($"{greedyPattern} finds no match.");
    
            // Match using lazy quantifier .+?.
            match = Regex.Match(input, lazyPattern);
            if (match.Success)
                Console.WriteLine($"Number at end of sentence (lazy): {match.Groups[1].Value}");
            else
                Console.WriteLine($"{lazyPattern} finds no match.");
        }
    }
    // The example displays the following output:
    //       Number at end of sentence (greedy): 5
    //       Number at end of sentence (lazy): 107325
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim greedyPattern As String = ".+(\d+)\."
            Dim lazyPattern As String = ".+?(\d+)\."
            Dim input As String = "This sentence ends with the number 107325."
            Dim match As Match
    
            ' Match using greedy quantifier .+.
            match = Regex.Match(input, greedyPattern)
            If match.Success Then
                Console.WriteLine("Number at end of sentence (greedy): {0}",
                                  match.Groups(1).Value)
            Else
                Console.WriteLine("{0} finds no match.", greedyPattern)
            End If
    
            ' Match using lazy quantifier .+?.
            match = Regex.Match(input, lazyPattern)
            If match.Success Then
                Console.WriteLine("Number at end of sentence (lazy): {0}",
                                  match.Groups(1).Value)
            Else
                Console.WriteLine("{0} finds no match.", lazyPattern)
            End If
        End Sub
    End Module
    ' The example displays the following output:
    '       Number at end of sentence (greedy): 5
    '       Number at end of sentence (lazy): 107325
    

    Описания "жадной" и "ленивой" версий этого регулярного выражения представлены в следующей таблице.

    Расписание Описание
    .+ ("жадный" квантификатор) Найти хотя бы одно совпадение с любым символом. Это заставляет обработчик регулярных выражений сопоставлять всю строку, а затем выполнять возврат, если это необходимо, для сопоставления оставшейся части шаблона.
    .+? ("ленивый" квантификатор) Сопоставьте по крайней мере одно вхождение любого символа, но как можно меньше.
    (\d+) Соответствие как минимум одной цифре и назначение ее для первой захваченной группы.
    \. Сопоставьте точку.

    Дополнительные сведения о ленивых квантификаторах см. в статье Квантификаторы.

  • Положительный прогноз: (?=подвыражение). Эта функция позволяет механизму обратного поиска возвращаться на то же место в тексте после сопоставления подвыражения. Эта функция полезна для поиска по всему тексту, проверяя несколько шаблонов, начинающихся с одной и той же позиции. Она также позволяет движку проверять, существует ли подстрока в конце совпадения, не включая подстроку в сопоставленный текст. В следующем примере используется положительный поиск вперед для извлечения слов в предложении, после которых нет знаков препинания.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string pattern = @"\b[A-Z]+\b(?=\P{P})";
            string input = "If so, what comes next?";
            foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
                Console.WriteLine(match.Value);
        }
    }
    // The example displays the following output:
    //       If
    //       what
    //       comes
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim pattern As String = "\b[A-Z]+\b(?=\P{P})"
            Dim input As String = "If so, what comes next?"
            For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
                Console.WriteLine(match.Value)
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       If
    '       what
    '       comes
    

    Определение регулярного выражения \b[A-Z]+\b(?=\P{P}) показано в таблице ниже.

    Расписание Описание
    \b Совпадение должно начинаться на границе слова.
    [A-Z]+ Найти совпадение с любым алфавитным символом один или более раз. Поскольку метод Regex.Matches вызывается с параметром RegexOptions.IgnoreCase, при сравнении не учитывается регистр символов.
    \b Совпадение должно заканчиваться на границе слова.
    (?=\P{P}) Посмотрите вперед, чтобы определить, является ли следующий символ знаком препинания. Если это не так, совпадение считается успешным.

    Для получения дополнительной информации о выражениях положительного обзора см. Конструкции группировки.

  • Отрицательный просмотр вперед: (?!подвыражение). Эта функция добавляет возможность сопоставлять выражение только в том случае, если подвыражение не совпадает. Это мощный инструмент для прореживания поиска, так как часто проще задать выражение для случая, который нужно исключить, чем для случаев, которые необходимо включить. Например, трудно написать выражение для слов, которые не начинаются с "non". В следующем примере для их исключения используется отрицательный поиск.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string pattern = @"\b(?!non)\w+\b";
            string input = "Nonsense is not always non-functional.";
            foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
                Console.WriteLine(match.Value);
        }
    }
    // The example displays the following output:
    //       is
    //       not
    //       always
    //       functional
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim pattern As String = "\b(?!non)\w+\b"
            Dim input As String = "Nonsense is not always non-functional."
            For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
                Console.WriteLine(match.Value)
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       is
    '       not
    '       always
    '       functional
    

    Шаблон регулярного выражения \b(?!non)\w+\b определяется, как показано в следующей таблице.

    Расписание Описание
    \b Совпадение должно начинаться на границе слова.
    (?!non) Убедитесь, что текущая строка не начинается с "non". Если это происходит, совпадение не удалось.
    (\w+) Найти один или несколько символов слова.
    \b Совпадение должно заканчиваться на границе слова.

    Дополнительные сведения об утверждениях отрицательного поиска вперед см. в Конструкции группировки.

  • Условная оценка: (?(выражение)да|нет) и (?(имя)да|нет), где выражение — сопоставляемая часть выражения, имя — имя группы захвата, да — сопоставляемая строка, если выражение найдено в тексте или имя является верной непустой захваченной группой, а нет — сопоставляемая строка, если выражение не найдено или имя не является верной непустой захваченной группой. Эта функция позволяет движку выполнять поиск с использованием более чем одного альтернативного шаблона, в зависимости от результата сопоставления предыдущего подвыражения или нулевой ширины утверждения. Это более действенный вид обратной ссылки, позволяющий, например, искать соответствия части выражения в зависимости от соответствия предыдущей части выражения. Регулярное выражение в следующем примере соответствует абзацам, предназначенным для общего и внутреннего использования. Абзацы, предназначенные только для внутреннего использования, начинаются с тега <PRIVATE>. Шаблон регулярного выражения ^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$ использует условную оценку для назначения содержимого абзацев, предназначенных для общего и внутреннего использования, для отдельных захваченных групп. Поэтому эти абзацы можно обработать по-разному.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string input = "<PRIVATE> This is not for public consumption." + Environment.NewLine +
                           "But this is for public consumption." + Environment.NewLine +
                           "<PRIVATE> Again, this is confidential.\n";
            string pattern = @"^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$";
            string publicDocument = null, privateDocument = null;
    
            foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))
            {
                if (match.Groups[1].Success)
                {
                    privateDocument += match.Groups[1].Value + "\n";
                }
                else
                {
                    publicDocument += match.Groups[3].Value + "\n";
                    privateDocument += match.Groups[3].Value + "\n";
                }
            }
    
            Console.WriteLine("Private Document:");
            Console.WriteLine(privateDocument);
            Console.WriteLine("Public Document:");
            Console.WriteLine(publicDocument);
        }
    }
    // The example displays the following output:
    //    Private Document:
    //    This is not for public consumption.
    //    But this is for public consumption.
    //    Again, this is confidential.
    //
    //    Public Document:
    //    But this is for public consumption.
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim input As String = "<PRIVATE> This is not for public consumption." + vbCrLf + _
                                  "But this is for public consumption." + vbCrLf + _
                                  "<PRIVATE> Again, this is confidential." + vbCrLf
            Dim pattern As String = "^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$"
            Dim publicDocument As String = Nothing
            Dim privateDocument As String = Nothing
    
            For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)
                If match.Groups(1).Success Then
                    privateDocument += match.Groups(1).Value + vbCrLf
                Else
                    publicDocument += match.Groups(3).Value + vbCrLf
                    privateDocument += match.Groups(3).Value + vbCrLf
                End If
            Next
    
            Console.WriteLine("Private Document:")
            Console.WriteLine(privateDocument)
            Console.WriteLine("Public Document:")
            Console.WriteLine(publicDocument)
        End Sub
    End Module
    ' The example displays the following output:
    '    Private Document:
    '    This is not for public consumption.
    '    But this is for public consumption.
    '    Again, this is confidential.
    '    
    '    Public Document:
    '    But this is for public consumption.
    

    Шаблон регулярного выражения определяется, как показано в следующей таблице.

    Расписание Описание
    ^ Начните совпадение с начала строки.
    (?<Pvt>\<PRIVATE\>\s)? Соответствует нулю или одному вхождению цепочки <PRIVATE>, после которого следует символ пробела. Назначьте соответствие захватывающей группе с именем Pvt.
    (?(Pvt)((\w+\p{P}?\s)+) Если захваченная группа Pvt существует, совпадают с одним или несколькими вхождениями одного или нескольких символов слова, за которыми следует 0 или 1 пунктуационный разделитель, после которых следует пробел. Присвойте подстроку первой захватывающей группе.
    |((\w+\p{P}?\s)+)) Если захваченная группа Pvt не существует, нужно сопоставить одно или несколько вхождений одного или нескольких символов слова, за которыми следует 0 или 1 пунктуационный разделитель, за которым идёт символ пробела. Присвойте подстроку третьей захватывающей группе.
    \r?$ Совпадение с концом строки или концом текста.

    Дополнительные сведения об условной оценке см. в статье Альтернативные конструкции.

  • Сбалансированные определения группы: (?<имя1-имя2>часть_выражения). Эта функция позволяет обработчику регулярных выражений отслеживать вложенные конструкции, такие как скобки или открывающие и закрывающие круглые скобки. Пример см. в разделе Конструкции группировки.

  • Атомарные группы: (?>часть выражения). Эта функция позволяет механизму обратного поиска гарантировать, что подвыражение соответствует только первому найденному совпадению, как если бы выражение выполнялось независимо от содержащего его выражения. Без этой конструкции поиск с возвратом в большем выражении может изменить поведение подвыражения. Например, регулярное выражение (a+)\w соответствует одному или нескольким символам "a", а также символу слова, которое следует за последовательностью символов "a", и присваивает последовательность символов "a" первой группе захвата. Но если последний символ входной строки также является "a", он соответствует элементу языка \w и не включается в захваченную группу.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string[] inputs = { "aaaaa", "aaaaab" };
            string backtrackingPattern = @"(a+)\w";
            Match match;
    
            foreach (string input in inputs)
            {
                Console.WriteLine($"Input: {input}");
                match = Regex.Match(input, backtrackingPattern);
                Console.WriteLine($"   Pattern: {backtrackingPattern}");
                if (match.Success)
                {
                    Console.WriteLine($"      Match: {match.Value}");
                    Console.WriteLine($"      Group 1: {match.Groups[1].Value}");
                }
                else
                {
                    Console.WriteLine("      Match failed.");
                }
            }
            Console.WriteLine();
        }
    }
    // The example displays the following output:
    //       Input: aaaaa
    //          Pattern: (a+)\w
    //             Match: aaaaa
    //             Group 1: aaaa
    //       Input: aaaaab
    //          Pattern: (a+)\w
    //             Match: aaaaab
    //             Group 1: aaaaa
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim inputs() As String = {"aaaaa", "aaaaab"}
            Dim backtrackingPattern As String = "(a+)\w"
            Dim match As Match
    
            For Each input As String In inputs
                Console.WriteLine("Input: {0}", input)
                match = Regex.Match(input, backtrackingPattern)
                Console.WriteLine("   Pattern: {0}", backtrackingPattern)
                If match.Success Then
                    Console.WriteLine("      Match: {0}", match.Value)
                    Console.WriteLine("      Group 1: {0}", match.Groups(1).Value)
                Else
                    Console.WriteLine("      Match failed.")
                End If
            Next
            Console.WriteLine()
        End Sub
    End Module
    ' The example displays the following output:
    '       Input: aaaaa
    '          Pattern: (a+)\w
    '             Match: aaaaa
    '             Group 1: aaaa
    '       Input: aaaaab
    '          Pattern: (a+)\w
    '             Match: aaaaab
    '             Group 1: aaaaa
    

    Регулярное выражение ((?>a+))\w препятствует такому поведению. Поскольку все последовательные символы "a" совпадают без обратного поиска, первая сохраненная группа включает все последовательные символы "a". Если после символов "a" не следует ни один символ, отличный от "a", соответствие считается неудачным.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string[] inputs = { "aaaaa", "aaaaab" };
            string nonbacktrackingPattern = @"((?>a+))\w";
            Match match;
    
            foreach (string input in inputs)
            {
                Console.WriteLine($"Input: {input}");
                match = Regex.Match(input, nonbacktrackingPattern);
                Console.WriteLine($"   Pattern: {nonbacktrackingPattern}");
                if (match.Success)
                {
                    Console.WriteLine($"      Match: {match.Value}");
                    Console.WriteLine($"      Group 1: {match.Groups[1].Value}");
                }
                else
                {
                    Console.WriteLine("      Match failed.");
                }
            }
            Console.WriteLine();
        }
    }
    // The example displays the following output:
    //       Input: aaaaa
    //          Pattern: ((?>a+))\w
    //             Match failed.
    //       Input: aaaaab
    //          Pattern: ((?>a+))\w
    //             Match: aaaaab
    //             Group 1: aaaaa
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim inputs() As String = {"aaaaa", "aaaaab"}
            Dim nonbacktrackingPattern As String = "((?>a+))\w"
            Dim match As Match
    
            For Each input As String In inputs
                Console.WriteLine("Input: {0}", input)
                match = Regex.Match(input, nonbacktrackingPattern)
                Console.WriteLine("   Pattern: {0}", nonbacktrackingPattern)
                If match.Success Then
                    Console.WriteLine("      Match: {0}", match.Value)
                    Console.WriteLine("      Group 1: {0}", match.Groups(1).Value)
                Else
                    Console.WriteLine("      Match failed.")
                End If
            Next
            Console.WriteLine()
        End Sub
    End Module
    ' The example displays the following output:
    '       Input: aaaaa
    '          Pattern: ((?>a+))\w
    '             Match failed.
    '       Input: aaaaab
    '          Pattern: ((?>a+))\w
    '             Match: aaaaab
    '             Group 1: aaaaa
    

    Дополнительные сведения об атомарных группах см. в статье Конструкции группировки.

  • Поиск совпадений справа налево, для применения которого нужно передать параметр RegexOptions.RightToLeft в конструктор класса Regex или в статический метод сопоставления экземпляров. Эта функция полезна при поиске справа налево вместо обычного поиска слева направо, а также бывает более эффективно начинать поиск с правой части шаблона, а не с левой. Как показано в примере ниже, использование поиска соответствий справа налево может изменить поведение жадных квантификаторов. В примере выполняются два поиска предложений, оканчивающихся на число. При поиске слева направо с использованием жадного квантификатора + имеется соответствие одной из шести цифр в предложении, тогда как при поиске справа налево — всем шести цифрам. Описание шаблона регулярного выражения см. в примере с ленивыми квантификаторами ранее в этом разделе.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string greedyPattern = @".+(\d+)\.";
            string input = "This sentence ends with the number 107325.";
            Match match;
    
            // Match from left-to-right using lazy quantifier .+?.
            match = Regex.Match(input, greedyPattern);
            if (match.Success)
                Console.WriteLine($"Number at end of sentence (left-to-right): {match.Groups[1].Value}");
            else
                Console.WriteLine($"{greedyPattern} finds no match.");
    
            // Match from right-to-left using greedy quantifier .+.
            match = Regex.Match(input, greedyPattern, RegexOptions.RightToLeft);
            if (match.Success)
                Console.WriteLine($"Number at end of sentence (right-to-left): {match.Groups[1].Value}");
            else
                Console.WriteLine($"{greedyPattern} finds no match.");
        }
    }
    // The example displays the following output:
    //       Number at end of sentence (left-to-right): 5
    //       Number at end of sentence (right-to-left): 107325
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim greedyPattern As String = ".+(\d+)\."
            Dim input As String = "This sentence ends with the number 107325."
            Dim match As Match
    
            ' Match from left-to-right using lazy quantifier .+?.
            match = Regex.Match(input, greedyPattern)
            If match.Success Then
                Console.WriteLine("Number at end of sentence (left-to-right): {0}",
                                  match.Groups(1).Value)
            Else
                Console.WriteLine("{0} finds no match.", greedyPattern)
            End If
    
            ' Match from right-to-left using greedy quantifier .+.
            match = Regex.Match(input, greedyPattern, RegexOptions.RightToLeft)
            If match.Success Then
                Console.WriteLine("Number at end of sentence (right-to-left): {0}",
                                  match.Groups(1).Value)
            Else
                Console.WriteLine("{0} finds no match.", greedyPattern)
            End If
        End Sub
    End Module
    ' The example displays the following output:
    '       Number at end of sentence (left-to-right): 5
    '       Number at end of sentence (right-to-left): 107325
    

    Дополнительные сведения о поиске соответствий справа налево см. в разделе Параметры регулярных выражений.

  • Положительный и отрицательный поиск назад: (?<=подвыражение) для положительного поиска назад и (?<!подвыражение) для отрицательного поиска назад. Эта функция аналогична предвосхищению, рассмотренному ранее в этом разделе. Поскольку обработчик регулярных выражений позволяет выполнять поиск справа налево, к регулярным выражениям можно применять поиск назад без каких-либо ограничений. С помощью положительного и отрицательного поиска назад также можно избегать вложенных квантификаторов, когда вложенная часть выражения является супермножеством внешнего выражения. Регулярные выражения с такими вложенными квантификаторами часто являются причиной низкой производительности. В следующем примере выполняется проверка, начинается ли и оканчивается ли строка с буквы или цифры, а также является ли любой другой символ в строке символом большего подмножества. В результате формируется часть регулярного выражения для проверки адресов электронной почты. Дополнительные сведения см. в статье Практическое руководство. Проверка строк на соответствие формату электронной почты.

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
        public static void Main()
        {
            string[] inputs = { "jack.sprat", "dog#", "dog#1", "me.myself",
                              "me.myself!" };
            string pattern = @"^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$";
            foreach (string input in inputs)
            {
                if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
                    Console.WriteLine($"{input}: Valid");
                else
                    Console.WriteLine($"{input}: Invalid");
            }
        }
    }
    // The example displays the following output:
    //       jack.sprat: Valid
    //       dog#: Invalid
    //       dog#1: Valid
    //       me.myself: Valid
    //       me.myself!: Invalid
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim inputs() As String = {"jack.sprat", "dog#", "dog#1", "me.myself",
                                       "me.myself!"}
            Dim pattern As String = "^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$"
            For Each input As String In inputs
                If Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase) Then
                    Console.WriteLine("{0}: Valid", input)
                Else
                    Console.WriteLine("{0}: Invalid", input)
                End If
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       jack.sprat: Valid
    '       dog#: Invalid
    '       dog#1: Valid
    '       me.myself: Valid
    '       me.myself!: Invalid
    

    Определение регулярного выражения ^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$ показано в таблице ниже.

    Расписание Описание
    ^ Начать совпадение в начале строки.
    [A-Z0-9] Соответствие любому числовому или алфавитно-цифровому символу. (При сравнении регистр не учитывается.)
    ([-!#$%&'.*+/=?^`{}|~\w])* Совпадает с нулем или более вхождениями любого символа, входящего в состав слова, или любого из следующих символов: -, !, #, $, %, &, ', ., *, +, /, =, ?, ^, `, {, }, | или ~.
    (?<=[A-Z0-9]) Посмотрите назад на предыдущий символ, который должен быть числом или буквенно-цифровым. (При сравнении регистр не учитывается.)
    $ Совпадение должно завершаться в конце строки.

    Дополнительные сведения о положительном и отрицательном просмотре назад см. в статье Конструкции группировки в регулярных выражениях.

Заголовок Описание
Поиск с возвратом Предоставляет информацию о том, как механизм возвратного поиска в регулярных выражениях находит альтернативные соответствия.
Компиляция и многократное использование Сведения о компиляции и многократном использовании регулярных выражений для повышения производительности.
Регулярные выражения .NET Общие сведения о регулярных выражениях в контексте языка программирования.
Объектная модель регулярных выражений Сведения об использовании классов регулярных выражений и примеры кода.
Элементы языка регулярных выражений — краткий справочник Сведения о наборе символов, операторов и конструкций, которые можно использовать для определения регулярных выражений.

Справочные материалы