如何在 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]+?,也可以根据实际输入进一步限制允许出现的词语。
备注:内容仅供参考。
