Here's a simpler example than the one in the document @Dav cited:
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);
}
result:
full match: foo%123%456%789
group #1: foo
group #2: 789
group #2 captures: 123, 456, 789
The full match
and group #1
results are straightforward, but the others require some explanation. Group #2, as you can see, is inside a non-capturing group that's controlled by a +
quantifier. It matches three times, but if you request its Value
, you only get what it matched the third time around--the final capture. Similarly, if you use the $2
placeholder in a replacement string, the final capture is what gets inserted in its place.
In most regex flavors, that's all you can get; each intermediate capture is overwritten by the next and lost; .NET is almost unique in preserving all of the captures and making them available after the match is performed. You can access them directly as I did here, or iterate through the CaptureCollection
as you would a MatchCollection
. There's no equivalent for the $1
-style replacement-string placeholders, though.
So the reason the API design is so ugly (as you put it) is twofold: first it was adapted from Perl's integral regex support to .NET's object-oriented framework; then the CaptureCollection
structure was grafted onto it. Perl 6 offers a much cleaner solution, but the authors accomplished that by rewriting Perl practically from scratch and throwing backward compatibility out the window.
Let me try to explain this with an example.
Consider the following text:
http://stackoverflow.com/
https://stackoverflow.com/questions/tagged/regex
Now, if I apply the regex below over it (I did not escape the slashes for clarity; when using it, slashes would have to be escaped to \/
)...
(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)? // slashes not escaped for clarity
(https?|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)? // slashes escaped
... I would get the following result:
Match "http://stackoverflow.com/"
Group 1: "http"
Group 2: "stackoverflow.com"
Group 3: "/"
Match "https://stackoverflow.com/questions/tagged/regex"
Group 1: "https"
Group 2: "stackoverflow.com"
Group 3: "/questions/tagged/regex"
But I don't care about the protocol -- I just want the host and path of the URL. So, I change the regex to include the non-capturing group (?:)
.
(?:https?|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)? // slashes escaped
Now, my result looks like this:
Match "http://stackoverflow.com/"
Group 1: "stackoverflow.com"
Group 2: "/"
Match "https://stackoverflow.com/questions/tagged/regex"
Group 1: "stackoverflow.com"
Group 2: "/questions/tagged/regex"
See? The first group has not been captured. The parser uses it to match the text, but ignores it later, in the final result.
EDIT:
As requested, let me try to explain groups too.
Well, groups serve many purposes. They can help you to extract exact information from a bigger match (which can also be named), they let you rematch a previous matched group, and can be used for substitutions. Let's try some examples, shall we?
Imagine you have some kind of XML or HTML (be aware that regex may not be the best tool for the job, but it is nice as an example). You want to parse the tags, so you could do something like this (I have added spaces to make it easier to understand):
\<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
\<(.+?)\> [^<]*? \</\1\>
The first regex has a named group (TAG), while the second one uses a common group. Both regexes do the same thing: they use the value from the first group (the name of the tag) to match the closing tag. The difference is that the first one uses the name to match the value, and the second one uses the group index (which starts at 1).
Let's try some substitutions now. Consider the following text:
Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.
Now, let's use this dumb regex over it:
\b(\S)(\S)(\S)(\S*)\b
This regex matches words with at least 3 characters, and uses groups to separate the first three letters. The result is this:
Match "Lorem"
Group 1: "L"
Group 2: "o"
Group 3: "r"
Group 4: "em"
Match "ipsum"
Group 1: "i"
Group 2: "p"
Group 3: "s"
Group 4: "um"
...
Match "consectetuer"
Group 1: "c"
Group 2: "o"
Group 3: "n"
Group 4: "sectetuer"
...
So, if we apply the substitution string:
$1_$3$2_$4
... over it, we are trying to use the first group, add an underscore, use the third group, then the second group, add another underscore, and then the fourth group. The resulting string would be like the one below.
L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.
You can use named groups for substitutions too, using ${name}
.
To play around with regexes, I recommend http://regex101.com/, which offers a good amount of details on how the regex works; it also offers a few regex engines to choose from.
Best Answer
You won't be the first who's fuzzy about it. Here's what the famous Jeffrey Friedl has to say about it (pages 437+):
And further on:
And a few pages later, this is his conclusion:
In other words: they are very similar, but occasionally and as it happens, you'll find a use for them. Before you grow another grey beard, you may even get fond of the Captures...
Since neither the above, nor what's said in the other post really seems to answer your question, consider the following. Think of Captures as a kind of history tracker. When the regex makes his match, it goes through the string from left to right (ignoring backtracking for a moment) and when it encounters a matching capturing parentheses, it will store that in
$x
(x being any digit), let's say$1
.Normal regex engines, when the capturing parentheses are to be repeated, will throw away the current
$1
and will replace it with the new value. Not .NET, which will keep this history and places it inCaptures[0]
.If we change your regex to look as follows:
you will notice that the first
Group
will have oneCaptures
(the first group always being the whole match, i.e., equal to$0
) and the second group will hold{S}
, i.e. only the last matching group. However, and here's the catch, if you want to find the other two catches, they're inCaptures
, which contains all intermediary captures for{Q}
{R}
and{S}
.If you ever wondered how you could get from the multiple-capture, which only shows last match to the individual captures that are clearly there in the string, you must use
Captures
.A final word on your final question: the total match always has one total Capture, don't mix that with the individual Groups. Captures are only interesting inside groups.