Named and Non-Capturing Groups in .NET Regular Expressions

The previous article introduced the .NET Group and GroupCollection classes, explained their uses and place in the .NET regular expression class hierarchy, and gave an example of how to define and use groups to extract or isolate sub-matches of a regular expression match. This article covers two more facets of using groups: creating named groups and defining non-capturing groups.

Named Groups

As a refresher, the following is an example of a simple regular expression pattern for North American phone numbers. The parentheses define the group, isolating the area code of the phone number:


The following snippet shows how to instantiate a Regex object using this pattern, how to enumerate the resulting matches, and then how to extract the specific group from each match's Groups member (The Match::Groups member is a collection of type GroupCollection that contains all the Group objects related to the match.):

String* input   = S"test: 404-555-1212 and 770-555-1212";
String* pattern = S"(\\d{3})-\\d{3}-\\d{4}";

Regex* rex = new Regex(pattern);

// for all the matches
for (Match* match = rex->Match(input);
      match = match->NextMatch())

This works, but the index value into the Groups object is hard-coded, which is a problem. (The value I used is 1 because the entire match is the first entry in the Groups collection and referenced at 0.) As a result, if at a later date, you or someone else disturbs the pattern such that the second group in the Groups object is not the area code, the code depending on this order will fail. This tip illustrates how to name your groups such as to isolate them from changes to the pattern.

The syntax for naming a group, while not very intuitive, is easy and looks like this:


Therefore, in order to name the area code group, you would simply alter the pattern as follows:


Now that the group is named, you just need to know how to extract it. The previous code snippet used the Groups object's Item indexer property to extract the desired group by index value. This is because the Groups object is an instance of the GroupCollection class and, like almost all collection classes, the Item property takes an numeric parameter that represents the desired object's place in the collection: 0 for the first object, 1 for the second object, and so on.

In the case of the GroupCollection::Item property, it is overloaded to also accept a value of type String that represents the actual name given to the group. Therefore, using the example where I gave the name of AreaCode to the group, I can now extract that value using either of these forms:


The second (named group) form is preferred and strongly recommended as it better isolates the code from the pattern changing.

Named and Non-Capturing Groups in .NET Regular Expressions

Non-Capturing Groups

Groups are not always defined in order to create sub-matches. Sometimes, groups get created as a side effect of the parenthetical syntax used to isolate a part of the expression such that modifiers, operators, or quantifiers can act on the isolated part of the expression. Irregardless of your reason for isolating a part of the pattern, once you do it using the parenthesis symbols, the regular expressions parser creates Group objects for that group within each Match object's group collection (Groups).

An example might better explain what I mean. Say you have a pattern to search a string for occurrences of the words "on", "an", and "in":


If you tested this pattern with the following function, which simply displays all the groups of each match, you'd find that each match results in five groups:

void DisplayGroups(String* input, String* pattern)
    StringBuilder* results = new StringBuilder();

    Regex* rex = new Regex(pattern);

    // for all the matches
    for (Match* match = rex->Match(input);
         match = match->NextMatch())
      results->AppendFormat(S"Match {0} at {1}\r\n",

      // for all of THIS match's groups
      GroupCollection* groups = match->Groups;
      for (int i = 0; i < groups->Count; i++)
        results->AppendFormat(S"\tGroup {0} at {1}\r\n",

  catch(Exception* pe)

Figure 1 shows the results of running the following code using the DisplayGroups function:

// Example usage of the DisplayGroups function
DisplayGroups(S"Tommy sat on a chair, in a room",


Figure 1: Using Parentheses in Patterns Always Creates Groups

Therefore, for efficiency—especially if you're processing huge amounts of data with your regular expressions, define the group as "non-capturing" by giving your group a blank name as follows:


If you run this pattern through the DisplayGroups function, you'll see that the only groups created represent the entire match (see Figure 2). (There is no way to eliminate that group.)


Figure 2: The Only Groups Created Represent the Entire Match

Looking Ahead

The previous two articles covered groups and group collections in regular expressions. However, one area that is closely tied to groups that I haven't touched on yet is captures. Therefore, the upcoming articles will explain what captures are and how they related to matches and groups.

About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.


  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Organizations today are under tremendous pressure to deliver a higher quality of products and services at lower costs, and to do so using existing resources. Any expenditure companies do make to help them achieve this goal is expected to deliver a measurable, hard-dollar ROI — and to deliver it quickly. The Internet of Things (IoT) is creating new opportunities for companies to enhance their products, gain business insights and differentiate their offerings. This whitepaper defines an ROI model for …

  • On-demand Event You can scale your mobile apps' data throughput and storage elastically with a fully managed, planet-scale NoSQL database service built for fast and predictable performance, high availability, elastic scaling, global distribution, and ease of development. This 40-minute on-demand webinar will demonstrate the scalability of DocumentDB and explore how the MongoDB API for DocumentDB, along with Xamarin, can simplify mobile application development for your organization. About Azure …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date