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

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

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

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

Внимание

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

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

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

Примечание.

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

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

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

В следующем примере показано, как использовать это регулярное выражение. Пять из девяти цифр-групп в входной строке соответствуют шаблону и четыре (95, 9219929и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.

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

Расписание Description
\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.

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

Расписание Description
\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.

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

Расписание Description
\b Начало на границе слова.
an? Соответствует нулю или одному n символуa.
\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.

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

Расписание Description
\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.

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

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

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

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

В следующем примере с помощью регулярного выражения (00\s){2,4} осуществляется поиск от двух до четырех вхождений двух нулей, за которыми следует пробел. Последняя часть входной строки включает этот шаблон пять раз, а не максимум 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.

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

Расписание Description
\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.

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

Расписание Description
^ Соответствует началу входного потока.
\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 не соответствует или mycompany.comне соответствуетmywebsite.

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.

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

Расписание Description
\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.

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

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

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

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

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

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

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

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

Рассмотрим регулярное выражение, которое предназначено для извлечения последних четырех цифр из строки чисел, например кредитной карта числа. Версия регулярного выражения с жадным квантификатором * будет выглядеть так: \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}. Оба регулярных выражения состоят из одной группы записи, которая определена в следующей таблице:

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

Первое регулярное выражение пытается сопоставить этот шаблон от нуля до двух раз. Второй — ровно два раза. Так как первый шаблон достигает минимального количества захватов с его первым захватом String.Empty, он никогда не повторяется, чтобы попытаться сопоставить a\1. Квантификатор {0,2} разрешает только пустые совпадения в последней итерации. В отличие от этого, второе регулярное выражение совпадает aa\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.

См. также