Вот более простой пример, чем приведенный в документе @Dav:
string s0 = @"foo%123%456%789";
Regex r0 = new Regex(@"^([a-z]+)(?:%([0-9]+))+$");
Match m0 = r0.Match(s0);
if (m0.Success)
{
Console.WriteLine(@"full match: {0}", m0.Value);
Console.WriteLine(@"group #1: {0}", m0.Groups[1].Value);
Console.WriteLine(@"group #2: {0}", m0.Groups[2].Value);
Console.WriteLine(@"group #2 captures: {0}, {1}, {2}",
m0.Groups[2].Captures[0].Value,
m0.Groups[2].Captures[1].Value,
m0.Groups[2].Captures[2].Value);
}
результат:
full match: foo%123%456%789
group #1: foo
group #2: 789
group #2 captures: 123, 456, 789
Результаты full match
и group #1
просты, но другие требуют некоторого объяснения. Группа № 2, как вы можете видеть, находится внутри группы без захвата, которая контролируется квантификатором +
. Он совпадает три раза, но если вы запросите его Value
, вы получите только то, что соответствовало ему в третий раз - окончательный захват. Аналогично, если вы используете заполнитель $2
в строке замены, окончательный захват - это то, что вставляется на его место.
В большинстве ароматов регулярных выражений это все, что вы можете получить; каждый промежуточный захват перезаписывается следующим и теряется; .NET практически уникален тем, что сохраняет все снимки и делает их доступными после завершения матча. Вы можете получить к ним прямой доступ, как я сделал здесь, или пройти по CaptureCollection
, как если бы вы MatchCollection
. Тем не менее, нет эквивалента для заполнителей строки замены $1
в стиле.
Таким образом, причина, по которой дизайн API так уродлив (как вы сказали), двояка: сначала он был адаптирован из встроенной поддержки регулярных выражений в Perl к объектно-ориентированной среде .NET; затем на него была привита структура CaptureCollection
. Perl 6 предлагает намного более чистое решение, но авторы достигли этого, переписав Perl практически с нуля и выбросив обратную совместимость из окна.