PAYATHON 2026

如何在 C# 中获取带 named groups 的多个 RegEx matches

技术问小赵

结论

Regex.Match(input)只返回第一个匹配结果。要获取所有重复内容,应使用 Regex.Matches(input)。它会返回一个 MatchCollection,可以遍历其中的每个 Match,再读取对应的 named groups。

如果只需要从第一个匹配继续查找,也可以调用 match.NextMatch()。处理多条匹配时,Matches()更直接。

使用正确的 named groups 语法

C# 正则表达式的命名捕获组写法如下:

(?<GroupName>pattern)

例如,可以分别为信用类型、金额和两个用电量边界命名:

string pattern = @"A Residential Usage (?<CreditType>[\w\s]+?) of \p{Sc}(?<Amount>\s?\d+(?:[., ]\d+)*) will be applied for each billing cycle in which YOUR USAGE falls between (?<MinUsage>[0-9, ]+) kWh and (?<MaxUsage>[0-9, ]+) kWh";

这里使用的是逐字字符串 @"...",所以 \w、\s 和 \p{Sc} 不需要再次转义。

获取全部 matches

Regex regex = new Regex(pattern);
MatchCollection matches = regex.Matches(input);

foreach (Match match in matches)
{
    Console.WriteLine($"CreditType: {match.Groups["CreditType"].Value.Trim()}");
    Console.WriteLine($"Amount: {match.Groups["Amount"].Value.Trim()}");
    Console.WriteLine($"MinUsage: {match.Groups["MinUsage"].Value.Trim()}");
    Console.WriteLine($"MaxUsage: {match.Groups["MaxUsage"].Value.Trim()}");
}

对于题目中的输入,这个循环会执行两次。第一条结果是:

CreditType: Credit
Amount: 80
MinUsage: 1,000
MaxUsage: 1,500

第二条结果是:

CreditType: Credit
Amount: 40
MinUsage: 1,501
MaxUsage: 2,000

重构为处理所有 named groups

原方法返回 string[],适合表示一条匹配记录。存在多个 matches 时,可以将返回类型改为 List<string[]>:

public static List<string[]> ValidatePattern(
    string pattern,
    string input,
    IReadOnlyList<string> groupNames)
{
    var regex = new Regex(pattern);
    MatchCollection matches = regex.Matches(input);

    var results = new List<string[]>();

    foreach (Match match in matches)
    {
        var values = new string[groupNames.Count];

        for (int i = 0; i < groupNames.Count; i++)
        {
            Group group = match.Groups[groupNames[i]];
            values[i] = group.Success ? group.Value : string.Empty;
        }

        results.Add(values);
    }

    return results;
}

调用方式如下:

var groupNames = new[]
{
    "CreditType",
    "Amount",
    "MinUsage",
    "MaxUsage"
};

List<string[]> results = ValidatePattern(pattern, input, groupNames);

foreach (string[] result in results)
{
    Console.WriteLine(string.Join(" | ", result));
}

如果没有匹配项,该方法会返回空列表。这样调用方可以直接遍历结果或检查 results.Count,不需要先判断是否为 null。

使用 Dictionary 保留组名

如果调用方还需要知道每个值属于哪个组,可以返回字典列表:

public static List<Dictionary<string, string>> ValidatePattern(
    string pattern,
    string input,
    IReadOnlyList<string> groupNames)
{
    var regex = new Regex(pattern);
    var results = new List<Dictionary<string, string>>();

    foreach (Match match in regex.Matches(input))
    {
        var groups = new Dictionary<string, string>();

        foreach (string name in groupNames)
        {
            Group group = match.Groups[name];
            groups[name] = group.Success ? group.Value : string.Empty;
        }

        results.Add(groups);
    }

    return results;
}

使用时可以按名称读取各个值:

foreach (var result in ValidatePattern(pattern, input, groupNames))
{
    Console.WriteLine(
        $"{result["CreditType"]}: ${result["Amount"]}, " +
        $"{result["MinUsage"]}-{result["MaxUsage"]} kWh");
}

只获取第二个 match

如果只是临时读取第二条匹配,可以直接使用索引:

MatchCollection matches = regex.Matches(input);

if (matches.Count > 1)
{
    Match secondMatch = matches[1];
    string minUsage = secondMatch.Groups["MinUsage"].Value;
    string maxUsage = secondMatch.Groups["MaxUsage"].Value;
}

也可以从第一个结果继续匹配:

Match firstMatch = regex.Match(input);
Match secondMatch = firstMatch.Success
    ? firstMatch.NextMatch()
    : Match.Empty;

if (secondMatch.Success)
{
    Console.WriteLine(secondMatch.Groups["MinUsage"].Value);
}

如果需要处理任意数量的重复内容,仍应使用 Regex.Matches()。

注意事项

  • Groups[name].Value 返回当前 Match 中对应命名组的值。这里的两句话会产生两个独立的 Match,所以需要遍历 MatchCollection,不能只检查第一个 Match 的 Captures。
  • Group.Captures 用于处理同一次匹配中某个捕获组被重复捕获的情况,不能代替对多个 matches 的遍历。
  • \p{Sc}匹配 Unicode 货币符号。\p{Sc}*允许货币符号不出现,也允许出现多个货币符号。如果输入中必须包含一个货币符号,应使用 \p{Sc}。
  • [0-9, ]+可能匹配逗号或空格位置不正确的内容。如果之后需要把金额或用电量转换为数值,应先确定允许的数字格式,再使用合适的 CultureInfo 解析。
  • [\w\s]+可能跨越换行并匹配过多文本。可以使用非贪婪形式 [\w\s]+?,也可以根据实际输入进一步限制允许出现的词语。

备注:内容仅供参考。