кванторы в регулярных выражениях

Квантификаторы определяют количество экземпляров символа, группы или класса символов, которое должно присутствовать во входных данных, чтобы было зафиксировано совпадение. В следующей таблице перечислены квантификаторы, поддерживаемые .NET:

Жадный квантификатор Ленивый квантификатор Описание
* *? Соответствует нулю или более раз.
+ +? Совпадает один или несколько раз.
? ?? Соответствует нулю или один раз.
{N} {N}? Соответствует ровно n раз.
{N,} {N,}? Совпадения по крайней мере n раз.
{N,М} {N,М}? Совпадения от n до m раз.

Количества n и m являются целочисленными константами. Обычно квантификаторы жадны. Они приводят к тому, что обработчик регулярных выражений соответствует максимальному числу вхождений определенных шаблонов. Добавление символа в ? квантификатор делает его ленивым. Это приводит к тому, что обработчик регулярных выражений будет соответствовать как можно меньшему числу вхождений. Полное описание разницы между жадными и ленивыми квантификаторами см. в разделе Greedy and Lazy Quantifiers далее в этой статье.

Важно!

Вложенные квантификаторы, такие как шаблон (a*)*регулярного выражения, могут увеличить количество сравнений, которые должен выполнять обработчик регулярных выражений. Число сравнений может увеличиваться как экспоненциальная функция числа символов во входной строке. Дополнительные сведения об этом поведении и способах его обхода см. в статье о поиске с возвратом.

Квантификаторы регулярных выражений

В следующих разделах перечислены квантификаторы, поддерживаемые регулярными выражениями .NET.

Примечание

Если в шаблоне регулярных выражений встречаются символы *, +, ?, { или }, обработчик регулярных выражений интерпретирует их как квантификаторы или как часть конструкций квантификаторов, если они не включены в класс символов. Чтобы они интерпретировались как символы-литералы за пределами класса символов, необходимо ставить перед ними escape-символ — обратную косую черту. Например, строка \* в шаблоне регулярного выражения интерпретируется как литеральный символ звездочки ("*").

Совпадение ноль или несколько раз: *

Квалификатор * выделяет предыдущий элемент, повторяющийся ноль или более раз. Он эквивалентен квантификатору {0,} . * — жадный квантификатор, ленивым эквивалентом которого является квантификатор *?.

В следующем примере показано, как использовать это регулярное выражение. Пять из девяти групп цифр во входной строке соответствуют шаблону, а четыре (95, 929, 9219и 9919) — нет.

string pattern = @"\b91*9*\b";
string input = "99 95 919 929 9119 9219 999 9919 91119";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:
//       '99' found at position 0.
//       '919' found at position 6.
//       '9119' found at position 14.
//       '999' found at position 24.
//       '91119' found at position 33.
Dim pattern As String = "\b91*9*\b"
Dim input As String = "99 95 919 929 9119 9219 999 9919 91119"
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       '99' found at position 0.
'       '919' found at position 6.
'       '9119' found at position 14.
'       '999' found at position 24.
'       '91119' found at position 33.

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

Шаблон Описание
\b Указывает, что совпадение должно начинаться с границы слова.
91* Соответствует объекту , 9 за которым следует ноль или более 1 символов.
9* Соответствует нулю или нескольким 9 символам.
\b Указывает, что совпадение должно заканчиваться на границе слова.

Совпадение один или несколько раз: +

Квантификатор + сопоставляет предыдущий элемент один или несколько раз. Это эквивалентно {1,}. + — жадный квантификатор, ленивым эквивалентом которого является квантификатор +?.

Например, с помощью регулярного выражения \ban+\w*?\b осуществляется сопоставление целых слов, начинающихся с буквы a, за которой следует одна или несколько букв n. В следующем примере показано, как использовать это регулярное выражение. Регулярное выражение соответствует словам an, annual, announcement и antique и не соответствует словам autumn и all.

string pattern = @"\ban+\w*?\b";

string input = "Autumn is a great time for an annual announcement to all antique collectors.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:
//       'an' found at position 27.
//       'annual' found at position 30.
//       'announcement' found at position 37.
//       'antique' found at position 57.
Dim pattern As String = "\ban+\w*?\b"

Dim input As String = "Autumn is a great time for an annual announcement to all antique collectors."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'an' found at position 27.
'       'annual' found at position 30.
'       'announcement' found at position 37.
'       'antique' found at position 57.

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

Шаблон Описание
\b Начало на границе слова.
an+ Соответствует объекту , a за которым следует один или несколько n символов.
\w*? Соответствует слову ноль или несколько раз, но как можно меньше раз.
\b Конец на границе слова.

Совпадение ноль или один раз: ?

Квантификатор ? сопоставляет предыдущий элемент ноль или один раз. Это эквивалентно {0,1}. ? — жадный квантификатор, ленивым эквивалентом которого является квантификатор ??.

Например, регулярное выражение \ban?\b пытается сопоставить целые слова, начинающиеся с буквы a , за которой следует ноль или один экземпляр буквы n. Иными словами, предпринимается попытка найти слова a и an. В следующем примере показано это регулярное выражение:

string pattern = @"\ban?\b";
string input = "An amiable animal with a large snout and an animated nose.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:
//        'An' found at position 0.
//        'a' found at position 23.
//        'an' found at position 42.
Dim pattern As String = "\ban?\b"
Dim input As String = "An amiable animal with a large snout and an animated nose."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'An' found at position 0.
'       'a' found at position 23.
'       'an' found at position 42.

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

Шаблон Описание
\b Начало на границе слова.
an? Соответствует нулю a или одному n символу.
\b Конец на границе слова.

Совпадение ровно n раз: {n}

Квантификатор {n} сопоставляет предыдущий элемент ровно n раз, где n — любое целое число. {n} — жадный квантификатор, ленивым эквивалентом которого является квантификатор {n}?.

Например, с помощью регулярного выражения \b\d+\,\d{3}\b осуществляется поиск границы слова, за которой следует один или более десятичных знаков, еще три десятичных знака и граница слова. В следующем примере показано это регулярное выражение:

string pattern = @"\b\d+\,\d{3}\b";
string input = "Sales totaled 103,524 million in January, " +
                      "106,971 million in February, but only " +
                      "943 million in March.";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        '103,524' found at position 14.
//        '106,971' found at position 45.
Dim pattern As String = "\b\d+\,\d{3}\b"
Dim input As String = "Sales totaled 103,524 million in January, " + _
                      "106,971 million in February, but only " + _
                      "943 million in March."
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       '103,524' found at position 14.
'       '106,971' found at position 45.

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

Шаблон Описание
\b Начало на границе слова.
\d+ Соответствует одной или нескольким десятичным цифрам.
\, Соответствует символу-запятой.
\d{3} Соответствует трем десятичным цифрам.
\b Конец на границе слова.

Совпадение как минимум n раз: {n,}

Квантификатор {n,} сопоставляет предыдущий элемент как минимум n раз, где n — любое целое число. {n,} — жадный квантификатор, ленивым эквивалентом которого является квантификатор {n,}?.

Например, с помощью регулярного выражения \b\d{2,}\b\D+ осуществляется поиск границы слова, за которой следует по крайней мере два десятичных знака, граница слова и знак, не являющийся числом. В следующем примере показано, как использовать это регулярное выражение. Регулярное выражение не соответствует фразе "7 days" , так как оно содержит только одну десятичную цифру, но оно успешно соответствует фразам "10 weeks" и "300 years".

string pattern = @"\b\d{2,}\b\D+";
string input = "7 days, 10 weeks, 300 years";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        '10 weeks, ' found at position 8.
//        '300 years' found at position 18.
Dim pattern As String = "\b\d{2,}\b\D+"
Dim input As String = "7 days, 10 weeks, 300 years"
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       '10 weeks, ' found at position 8.
'       '300 years' found at position 18.

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

Шаблон Описание
\b Начало на границе слова.
\d{2,} Соответствует по крайней мере двум десятичным цифрам.
\b Соответствует границе слова.
\D+ Соответствует по крайней мере одной не десятичной цифре.

Совпадение от n до m раз: {n,m}

Квантификатор {n,m} сопоставляет предыдущий элемент минимум n раз, но не больше m раз, где n и m — целые числа. {n,m} — жадный квантификатор, ленивым эквивалентом которого является квантификатор {n,m}?.

В следующем примере с помощью регулярного выражения (00\s){2,4} осуществляется поиск от двух до четырех вхождений двух нулей, за которыми следует пробел. Заключительная часть входной строки включает этот шаблон пять раз, а не максимум четыре. Однако только начало этой части строки (до пробела и пятой пары нулей) соответствует шаблону регулярного выражения.

string pattern = @"(00\s){2,4}";
string input = "0x00 FF 00 00 18 17 FF 00 00 00 21 00 00 00 00 00";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        '00 00 ' found at position 8.
//        '00 00 00 ' found at position 23.
//        '00 00 00 00 ' found at position 35.
Dim pattern As String = "(00\s){2,4}"
Dim input As String = "0x00 FF 00 00 18 17 FF 00 00 00 21 00 00 00 00 00"
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       '00 00 ' found at position 8.
'       '00 00 00 ' found at position 23.
'       '00 00 00 00 ' found at position 35.

Совпадение ноль или несколько раз (ленивое совпадение): *?

Квантификатор *? соответствует предыдущему элементу ноль или больше раз, но как можно меньше. Это ленивый аналог жадного квантификатора *.

В следующем примере регулярное выражение \b\w*?oo\w*?\b сопоставляет все слова, которые содержат строку oo.

 string pattern = @"\b\w*?oo\w*?\b";
 string input = "woof root root rob oof woo woe";
 foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

 //  The example displays the following output:
//        'woof' found at position 0.
//        'root' found at position 5.
//        'root' found at position 10.
//        'oof' found at position 19.
//        'woo' found at position 23.
Dim pattern As String = "\b\w*?oo\w*?\b"
Dim input As String = "woof root root rob oof woo woe"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'woof' found at position 0.
'       'root' found at position 5.
'       'root' found at position 10.
'       'oof' found at position 19.
'       'woo' found at position 23.

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

Шаблон Описание
\b Начало на границе слова.
\w*? Соответствует нулю или нескольким символам слов, но как можно меньше символов.
oo Соответствует строке oo.
\w*? Соответствует нулю или нескольким символам слов, но как можно меньше символов.
\b Конец на границе слова.

Совпадение один или несколько раз (ленивое совпадение): +?

Квантификатор +? соответствует предыдущему элементу один или несколько раз, но как можно меньше. Это ленивый аналог жадного квантификатора +.

Например, регулярное выражение \b\w+?\b соответствует одному или нескольким символам, разделенным границами слов. В следующем примере показано это регулярное выражение:

string pattern = @"\b\w+?\b";
string input = "Aa Bb Cc Dd Ee Ff";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        'Aa' found at position 0.
//        'Bb' found at position 3.
//        'Cc' found at position 6.
//        'Dd' found at position 9.
//        'Ee' found at position 12.
//        'Ff' found at position 15.
Dim pattern As String = "\b\w+?\b"
Dim input As String = "Aa Bb Cc Dd Ee Ff"
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'Aa' found at position 0.
'       'Bb' found at position 3.
'       'Cc' found at position 6.
'       'Dd' found at position 9.
'       'Ee' found at position 12.
'       'Ff' found at position 15.

Совпадение ноль или один раз (ленивое совпадение): ??

Квантификатор ?? соответствует предыдущему элементу ноль или один раз, но как можно меньше раз. Это ленивый аналог жадного квантификатора ?.

Например, регулярное выражение ^\s*(System.)??Console.Write(Line)??\(?? пытается сопоставить строки Console.Write или Console.WriteLine. Строка также может содержать System. до Console, за которой следует открывающая круглая скобка. Искомый текст должен находиться в начале строки, хотя перед ним может стоять пробел. В следующем примере показано это регулярное выражение:

string pattern = @"^\s*(System.)??Console.Write(Line)??\(??";
string input = "System.Console.WriteLine(\"Hello!\")\n" +
                      "Console.Write(\"Hello!\")\n" +
                      "Console.WriteLine(\"Hello!\")\n" +
                      "Console.ReadLine()\n" +
                      "   Console.WriteLine";
foreach (Match match in Regex.Matches(input, pattern,
                                      RegexOptions.IgnorePatternWhitespace |
                                      RegexOptions.IgnoreCase |
                                      RegexOptions.Multiline))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        'System.Console.Write' found at position 0.
//        'Console.Write' found at position 36.
//        'Console.Write' found at position 61.
//        '   Console.Write' found at position 110.
Dim pattern As String = "^\s*(System.)??Console.Write(Line)??\(??"
Dim input As String = "System.Console.WriteLine(""Hello!"")" + vbCrLf + _
                      "Console.Write(""Hello!"")" + vbCrLf + _
                      "Console.WriteLine(""Hello!"")" + vbCrLf + _
                      "Console.ReadLine()" + vbCrLf + _
                      "   Console.WriteLine"
For Each match As Match In Regex.Matches(input, pattern, _
                                         RegexOptions.IgnorePatternWhitespace Or RegexOptions.IgnoreCase Or RegexOptions.MultiLine)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'System.Console.Write' found at position 0.
'       'Console.Write' found at position 36.
'       'Console.Write' found at position 61.
'       '   Console.Write' found at position 110.

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

Шаблон Описание
^ Соответствует началу входного потока.
\s* Соответствует нулю или более пробелам.
(System.)?? Соответствует нулю или одному вхождениям строки System..
Console.Write Соответствует строке Console.Write.
(Line)?? Соответствует нулю или одному вхождениям строки Line.
\(?? Соответствует нулю или одному вхождения открывающей круглых скобок.

Совпадение ровно n раз (ленивое совпадение): {n}?

Квантификатор {n}? сопоставляет предыдущий элемент ровно n раз, где n — любое целое число. Это ленивый аналог жадного квантификатора {n}.

В следующем примере регулярное выражение \b(\w{3,}?\.){2}?\w{3,}?\b используется для идентификации адреса веб-сайта. Выражение соответствует www.microsoft.com и msdn.microsoft.com , но не соответствует mywebsite или mycompany.com.

string pattern = @"\b(\w{3,}?\.){2}?\w{3,}?\b";
string input = "www.microsoft.com msdn.microsoft.com mywebsite mycompany.com";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        'www.microsoft.com' found at position 0.
//        'msdn.microsoft.com' found at position 18.
Dim pattern As String = "\b(\w{3,}?\.){2}?\w{3,}?\b"
Dim input As String = "www.microsoft.com msdn.microsoft.com mywebsite mycompany.com"
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'www.microsoft.com' found at position 0.
'       'msdn.microsoft.com' found at position 18.

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

Шаблон Описание
\b Начало на границе слова.
(\w{3,}?\.) Соответствует по крайней мере трем символам слова, но как можно меньше символов, за которым следует точка или точка. Этот шаблон является первой захватываемой группой.
(\w{3,}?\.){2}? Соответствует шаблону в первой группе два раза, но как можно меньше раз.
\b Сопоставление заканчивается на границе слова.

Совпадение как минимум n раз (ленивое совпадение): {n,}?

Квантификатор {n,}? соответствует предыдущему элементу по крайней мере n раз, где n — любое целое число, но как можно меньше раз. Это ленивый аналог жадного квантификатора {n,}.

Его поведение приводится в описании квантификатора {n}? в предыдущем разделе. В регулярном выражении из этого примера квантификатор {n,} используется для поиска строки, состоящей по крайней мере из трех символов, после которых стоит точка.

Совпадение от n до m раз (ленивое совпадение): {n,m}?

{ Квантификатор n,m}? соответствует предыдущему элементу между n и m разами, где n и m являются целыми числами, но как можно меньше раз. Это ленивый аналог жадного квантификатора {n,m}.

В следующем примере регулярное выражение \b[A-Z](\w*?\s*?){1,10}[.!?] соответствует предложениям, содержащим от 1 до 10 слов. Ему соответствуют все предложения в исходной строке кроме одного, длина которого составляет 18 слов.

string pattern = @"\b[A-Z](\w*?\s*?){1,10}[.!?]";
string input = "Hi. I am writing a short note. Its purpose is " +
                      "to test a regular expression that attempts to find " +
                      "sentences with ten or fewer words. Most sentences " +
                      "in this note are short.";
foreach (Match match in Regex.Matches(input, pattern))
   Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

//  The example displays the following output:
//        'Hi.' found at position 0.
//        'I am writing a short note.' found at position 4.
//        'Most sentences in this note are short.' found at position 132.
Dim pattern As String = "\b[A-Z](\w*\s?){1,10}?[.!?]"
Dim input As String = "Hi. I am writing a short note. Its purpose is " + _
                      "to test a regular expression that attempts to find " + _
                      "sentences with ten or fewer words. Most sentences " + _
                      "in this note are short."
For Each match As Match In Regex.Matches(input, pattern)
    Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
'       'Hi.' found at position 0.
'       'I am writing a short note.' found at position 4.
'       'Most sentences in this note are short.' found at position 132.

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

Шаблон Описание
\b Начало на границе слова.
[A-Z] Соответствует символу верхнего регистра от A до Z.
(\w*?\s*?) Соответствует нулю или нескольким символам слов, за которым следует один или несколько пробелов, но как можно меньше раз. Этот шаблон является первой захватываемой группой.
{1,10} Соответствует предыдущему шаблону от 1 до 10 раз.
[.!?] Соответствует любому из знаков .препинания , !или ?.

Жадные и ленивые квантификаторы

Некоторые квантификаторы имеют две версии:

  • Жадная версия.

    Жадный квантификатор пытается найти максимально возможное число соответствий элемента.

  • Нежадная (ленивая) версия.

    При использовании нежадных идентификаторов предпринимается попытка найти минимально возможное число соответствий элемента. Вы можете превратить жадный квантификатор в отложенный квантификатор, добавив .?

Рассмотрим регулярное выражение, предназначенное для извлечения последних четырех цифр из строки чисел, например кредитного карта числа. Версия регулярного выражения с жадным квантификатором * будет выглядеть так: \b.*([0-9]{4})\b. Однако если строка содержит два числа, это регулярное выражение соответствует последним четырем цифрам только второго числа, как показано в следующем примере:

string greedyPattern = @"\b.*([0-9]{4})\b";
string input1 = "1112223333 3992991999";
foreach (Match match in Regex.Matches(input1, greedyPattern))
   Console.WriteLine("Account ending in ******{0}.", match.Groups[1].Value);

// The example displays the following output:
//       Account ending in ******1999.
Dim greedyPattern As String = "\b.*([0-9]{4})\b"
Dim input1 As String = "1112223333 3992991999"
For Each match As Match In Regex.Matches(input1, greedypattern)
    Console.WriteLine("Account ending in ******{0}.", match.Groups(1).Value)
Next
' The example displays the following output:
'       Account ending in ******1999.

Регулярное выражение не совпадает с первым числом, так как квантификатор * ищет максимально возможное число совпадений во всей строке, и здесь он находит его в конце строки.

Такое поведение не является желаным. Вместо этого можно использовать *? отложенное квантификатор для извлечения цифр из обоих чисел, как показано в следующем примере:

string lazyPattern = @"\b.*?([0-9]{4})\b";
string input2 = "1112223333 3992991999";
foreach (Match match in Regex.Matches(input2, lazyPattern))
   Console.WriteLine("Account ending in ******{0}.", match.Groups[1].Value);

// The example displays the following output:
//       Account ending in ******3333.
//       Account ending in ******1999.
Dim lazyPattern As String = "\b.*?([0-9]{4})\b"
Dim input2 As String = "1112223333 3992991999"
For Each match As Match In Regex.Matches(input2, lazypattern)
    Console.WriteLine("Account ending in ******{0}.", match.Groups(1).Value)
Next
' The example displays the following output:
'       Account ending in ******3333.
'       Account ending in ******1999.

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

Квантификаторы и пустые соответствия

Квантификаторы *, + и {n,m} и их ленивые аналоги никогда не повторяются после пустого соответствия, если найдено минимальное количество совпадений. Это правило препятствует вхождению квантификаторов в бесконечные циклы при пустых соответствиях частей выражений, если максимальное количество возможных фиксаций группы бесконечно или приближено к бесконечному.

Например, в следующем коде показан результат вызова Regex.Match метода с шаблоном (a?)*регулярного выражения , который соответствует нулю или одному a символу ноль или более раз. Отдельная группа захвата захватывает все a и String.Empty, но второго пустого совпадения нет, так как первое пустое совпадение приводит к прекращению повторения квантификатора.

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = "(a?)*";
      string input = "aaabbb";
      Match match = Regex.Match(input, pattern);
      Console.WriteLine("Match: '{0}' at index {1}",
                        match.Value, match.Index);
      if (match.Groups.Count > 1) {
         GroupCollection groups = match.Groups;
         for (int grpCtr = 1; grpCtr <= groups.Count - 1; grpCtr++) {
            Console.WriteLine("   Group {0}: '{1}' at index {2}",
                              grpCtr,
                              groups[grpCtr].Value,
                              groups[grpCtr].Index);
            int captureCtr = 0;
            foreach (Capture capture in groups[grpCtr].Captures) {
               captureCtr++;
               Console.WriteLine("      Capture {0}: '{1}' at index {2}",
                                 captureCtr, capture.Value, capture.Index);
            }
         }
      }
   }
}
// The example displays the following output:
//       Match: 'aaa' at index 0
//          Group 1: '' at index 3
//             Capture 1: 'a' at index 0
//             Capture 2: 'a' at index 1
//             Capture 3: 'a' at index 2
//             Capture 4: '' at index 3
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "(a?)*"
        Dim input As String = "aaabbb"
        Dim match As Match = Regex.Match(input, pattern)
        Console.WriteLine("Match: '{0}' at index {1}",
                          match.Value, match.Index)
        If match.Groups.Count > 1 Then
            Dim groups As GroupCollection = match.Groups
            For grpCtr As Integer = 1 To groups.Count - 1
                Console.WriteLine("   Group {0}: '{1}' at index {2}",
                                  grpCtr,
                                  groups(grpCtr).Value,
                                  groups(grpCtr).Index)
                Dim captureCtr As Integer = 0
                For Each capture As Capture In groups(grpCtr).Captures
                    captureCtr += 1
                    Console.WriteLine("      Capture {0}: '{1}' at index {2}",
                                      captureCtr, capture.Value, capture.Index)
                Next
            Next
        End If
    End Sub
End Module
' The example displays the following output:
'       Match: 'aaa' at index 0
'          Group 1: '' at index 3
'             Capture 1: 'a' at index 0
'             Capture 2: 'a' at index 1
'             Capture 3: 'a' at index 2
'             Capture 4: '' at index 3

Чтобы увидеть практическое различие между захватываемой группой, определяющей минимальное и максимальное количество записей, и группой, определяющей фиксированное количество записей, воспользуйтесь шаблонами регулярных выражений (a\1|(?(1)\1)){0,2} и (a\1|(?(1)\1)){2}. Оба регулярных выражения состоят из одной захватываемой группы, которая определена в следующей таблице:

Шаблон Описание
(a\1 Либо совпадает a со значением первой захваченной группы ...
|(?(1) … или проверяет, определена ли первая захваченная группа. Конструкция (?(1) не определяет захватываемую группу.
\1)) Если первая захваченная группа существует, следует сопоставить ее значение. Если группа не существует, она будет соответствовать String.Empty.

Первое регулярное выражение пытается сопоставить этот шаблон от нуля до двух раз. Второй — ровно два раза. Так как первый шаблон достигает минимального числа записей с первым захватом String.Empty, он никогда не повторяется, чтобы попытаться сопоставить a\1. Квантификатор {0,2} допускает только пустые совпадения в последней итерации. В отличие от этого, второе регулярное выражение совпадает a , так как оно вычисляет a\1 второй раз. Минимальное количество итераций (2) заставляет подсистему повторяться после пустого совпадения.

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern, input;

      pattern = @"(a\1|(?(1)\1)){0,2}";
      input = "aaabbb";

      Console.WriteLine("Regex pattern: {0}", pattern);
      Match match = Regex.Match(input, pattern);
      Console.WriteLine("Match: '{0}' at position {1}.",
                        match.Value, match.Index);
      if (match.Groups.Count > 1) {
         for (int groupCtr = 1; groupCtr <= match.Groups.Count - 1; groupCtr++)
         {
            Group group = match.Groups[groupCtr];
            Console.WriteLine("   Group: {0}: '{1}' at position {2}.",
                              groupCtr, group.Value, group.Index);
            int captureCtr = 0;
            foreach (Capture capture in group.Captures) {
               captureCtr++;
               Console.WriteLine("      Capture: {0}: '{1}' at position {2}.",
                                 captureCtr, capture.Value, capture.Index);
            }
         }
      }
      Console.WriteLine();

      pattern = @"(a\1|(?(1)\1)){2}";
      Console.WriteLine("Regex pattern: {0}", pattern);
      match = Regex.Match(input, pattern);
         Console.WriteLine("Matched '{0}' at position {1}.",
                           match.Value, match.Index);
      if (match.Groups.Count > 1) {
         for (int groupCtr = 1; groupCtr <= match.Groups.Count - 1; groupCtr++)
         {
            Group group = match.Groups[groupCtr];
            Console.WriteLine("   Group: {0}: '{1}' at position {2}.",
                              groupCtr, group.Value, group.Index);
            int captureCtr = 0;
            foreach (Capture capture in group.Captures) {
               captureCtr++;
               Console.WriteLine("      Capture: {0}: '{1}' at position {2}.",
                                 captureCtr, capture.Value, capture.Index);
            }
         }
      }
   }
}
// The example displays the following output:
//       Regex pattern: (a\1|(?(1)\1)){0,2}
//       Match: '' at position 0.
//          Group: 1: '' at position 0.
//             Capture: 1: '' at position 0.
//
//       Regex pattern: (a\1|(?(1)\1)){2}
//       Matched 'a' at position 0.
//          Group: 1: 'a' at position 0.
//             Capture: 1: '' at position 0.
//             Capture: 2: 'a' at position 0.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern, input As String

        pattern = "(a\1|(?(1)\1)){0,2}"
        input = "aaabbb"

        Console.WriteLine("Regex pattern: {0}", pattern)
        Dim match As Match = Regex.Match(input, pattern)
        Console.WriteLine("Match: '{0}' at position {1}.",
                          match.Value, match.Index)
        If match.Groups.Count > 1 Then
            For groupCtr As Integer = 1 To match.Groups.Count - 1
                Dim group As Group = match.Groups(groupCtr)
                Console.WriteLine("   Group: {0}: '{1}' at position {2}.",
                                  groupCtr, group.Value, group.Index)
                Dim captureCtr As Integer = 0
                For Each capture As Capture In group.Captures
                    captureCtr += 1
                    Console.WriteLine("      Capture: {0}: '{1}' at position {2}.",
                                      captureCtr, capture.Value, capture.Index)
                Next
            Next
        End If
        Console.WriteLine()

        pattern = "(a\1|(?(1)\1)){2}"
        Console.WriteLine("Regex pattern: {0}", pattern)
        match = Regex.Match(input, pattern)
        Console.WriteLine("Matched '{0}' at position {1}.",
                          match.Value, match.Index)
        If match.Groups.Count > 1 Then
            For groupCtr As Integer = 1 To match.Groups.Count - 1
                Dim group As Group = match.Groups(groupCtr)
                Console.WriteLine("   Group: {0}: '{1}' at position {2}.",
                                  groupCtr, group.Value, group.Index)
                Dim captureCtr As Integer = 0
                For Each capture As Capture In group.Captures
                    captureCtr += 1
                    Console.WriteLine("      Capture: {0}: '{1}' at position {2}.",
                                      captureCtr, capture.Value, capture.Index)
                Next
            Next
        End If
    End Sub
End Module
' The example displays the following output:
'       Regex pattern: (a\1|(?(1)\1)){0,2}
'       Match: '' at position 0.
'          Group: 1: '' at position 0.
'             Capture: 1: '' at position 0.
'       
'       Regex pattern: (a\1|(?(1)\1)){2}
'       Matched 'a' at position 0.
'          Group: 1: 'a' at position 0.
'             Capture: 1: '' at position 0.
'             Capture: 2: 'a' at position 0.

См. также