写 C# 正则时,很多人一开始会用 | 处理“二选一”,但一旦模式稍微变复杂,就容易踩到分组范围和匹配顺序的坑;而回溯引用看起来语法短,实际却能解决“前后内容必须一致”这类很难用普通匹配表达的问题。本文把这两部分拆开讲清楚,并保留可直接运行的示例,帮助你判断什么时候该用分支、什么时候该把捕获组和回溯引用配合起来。
分支 | 是怎么工作的
分支用于在多个候选中选择一个,最基础的写法就是把候选项用 | 连起来。
Regex.IsMatch("my dog", @"cat|dog"); // True
这个表达式的意思很直接:只要字符串里能匹配到 cat 或 dog 其中之一,就返回 True。
为什么分支几乎总要配合分组
实际场景里,分支通常不会单独出现,而是要和分组一起控制范围。尤其是 (?:...) 这种非捕获分组,适合“只分组,不提取”。

Console.WriteLine( Regex.IsMatch("https://www.baidu.com", @"(?:http|https)://" )); // True
这里的重点不是提取 http 或 https,而是让分支只作用在协议这一段。
另一个常见例子是文件扩展名判断:
Console.WriteLine( Regex.IsMatch("a.png", @"^.+.(?:jpg|png|gif)$" )); // True
这段模式把结尾限制在 jpg、png、gif 三种扩展名之一。这里使用非捕获分组,能让表达式更清晰,也避免生成不必要的捕获结果。
分支的优先级:正则会从左到右尝试
分支还有一个很关键的规则:正则引擎通常会先尝试左边分支,只要左边已经满足条件,就不会继续看右边。
这意味着,候选项的排列顺序会直接影响结果。原文给出的示例如下:
Console.WriteLine( Regex.IsMatch("ab", @"^(?:ab|a)$" )); // True
这里之所以能正确匹配 ab,就是因为更长的 ab 被放在前面。实际编写时,一个常见经验是:如果多个分支存在包含关系,通常把更具体、更长的分支放前面,更不容易出现“短分支提前吃掉结果”的问题。
回溯引用是什么,适合解决什么问题
回溯引用的作用,是让后面的内容必须重复前面已经匹配到的内容。它依赖捕获组 (...) 才能工作。
- 数字回溯引用:
1、2……表示引用第 1、第 2 个捕获组 - 命名回溯引用:
k<name>,表示引用名为name的捕获组
如果你的需求里包含“前后必须一致”“重复之前出现的文本”“成对出现”这类约束,回溯引用通常就派得上用场。
示例一:匹配成对引号,并确保左右一致
先看一个很典型的场景:匹配 "hello" 或 'hello',但不允许出现 "hello' 这种左右引号不一致的情况。

正则:
^(["'])(?<content>.*)1$
这个模式可以拆成三部分理解:
(["']):先捕获一个引号字符,可能是单引号,也可能是双引号,这是第 1 组(?<content>.*):捕获中间内容,命名为content1:要求结尾必须再次出现与第 1 组完全相同的内容
也就是说,开头如果匹配到的是双引号,结尾就必须还是双引号;如果开头是单引号,结尾也必须是单引号。
string[] inputs = { ""hello"", "'hello'", ""hello'", "'hello"" };
var pattern = @"^([""'])(?<content>.*)1$";
foreach (var s in inputs)
{
var m = Regex.Match(s, pattern);
Console.WriteLine($"{s} -> {m.Success}");
}
这类写法比单纯判断“首尾是不是引号”更严格,因为它明确要求两端必须是同一种符号。
示例二:匹配重复单词
另一个常见用途,是找出连续重复的单词,例如 hello hello。
需求是:匹配两个相同单词,并且中间用空白分隔。
b(w+)s+1b
var text = "This is is a test, hello hello!";
var pattern = @"b(w+)s+1b";
foreach (Match m in Regex.Matches(text, pattern))
{
Console.WriteLine(m.Value); // "is is", "hello hello"
}
它的思路是:先捕获一个单词,再要求后面再次出现完全相同的内容。这样就能找出 is is 和 hello hello 这种重复词。
示例三:用命名回溯引用提高可读性
当表达式变长、捕获组变多时,只靠数字编号容易看乱。这时可以改用命名回溯引用,让模式更直观。
var text = "This is is a test, hello hello!";
var pattern = @"b(?<word>w+)s+k<word>b";
foreach (Match m in Regex.Matches(text, pattern))
{
Console.WriteLine(m.Value); // "is is", "hello hello"
}
和前一个例子相比,核心逻辑没有变,只是把“引用第几个组”改成了“引用名为 word 的组”。如果正则用于团队协作、后续维护,命名方式通常更容易读懂。
怎么判断该用分支还是回溯引用
可以用一个简单思路来区分:
- 如果你要表达“在多个候选里选一个”,优先想到分支
| - 如果你要表达“后面的内容必须和前面匹配到的一样”,优先想到回溯引用
其中,分支经常要配合分组控制范围;回溯引用则离不开捕获组。把这两套规则掌握后,很多看起来复杂的匹配条件,其实都能拆回“先捕获,再复用”或者“先分组,再选择”的基本写法。













