C# 正则表达式
`Regex` 类、分组、替换、命名组、以及超时保护。
Key Points把 `Regex` 实例缓存成静态字段,别在循环里反复 `new` —— 编译有成本。
基本匹配
C#
using System.Text.RegularExpressions;
var re = new Regex(@"\d{3}-\d{4}");
Console.WriteLine(re.IsMatch("电话 555-1234"));
Console.WriteLine(re.Match("电话 555-1234").Value);
var all = re.Matches("555-1234 和 666-5678");
Console.WriteLine(all.Count);
foreach (Match m in all)
{
Console.WriteLine(m.Value);
}Output
True
555-1234
2
555-1234
666-5678分组
C#
var re = new Regex(@"(\w+)@(\w+)\.com");
var m = re.Match("联系 tom@example.com");
Console.WriteLine(m.Success);
Console.WriteLine(m.Groups[0].Value); // 整体
Console.WriteLine(m.Groups[1].Value); // 第一组
Console.WriteLine(m.Groups[2].Value); // 第二组Output
True
tom@example.com
tom
exampleC#
// 命名分组
var re = new Regex(@"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})");
var m = re.Match("2026-10-31");
Console.WriteLine(m.Groups["year"].Value);
Console.WriteLine(m.Groups["month"].Value);
Console.WriteLine(m.Groups["day"].Value);Output
2026
10
31Named groups beat numeric indices: reorder the pattern and numeric indices break while names do not. Use names for anything non-trivial.
替换
C#
var re = new Regex(@"(\d{4})-(\d{2})-(\d{2})");
Console.WriteLine(re.Replace("2026-10-31", "$3/$2/$1"));
// 用委托做复杂替换
var result = re.Replace("2026-10-31", m => $"[{m.Groups[1].Value}年]");
Console.WriteLine(result);
// 去掉 HTML 标签
var tags = new Regex(@"<[^>]+>");
Console.WriteLine(tags.Replace("<p>你好</p>", ""));
// 转义用户输入
Console.WriteLine(Regex.Escape("1+1=2"));Output
31/10/2026
[2026年]
你好
1\+1\=2| Method | Returns |
|---|---|
IsMatch | bool |
Match | 第一个 Match |
Matches | MatchCollection |
Replace | 替换后的字符串 |
Split | 按正则拆分的数组 |
Regex.Escape | 转义特殊字符 |
选项
C#
// 忽略大小写
var re1 = new Regex("hello", RegexOptions.IgnoreCase);
Console.WriteLine(re1.IsMatch("HELLO"));
// 多行:^ 和 $ 匹配每行首尾
var re2 = new Regex(@"^\d+", RegexOptions.Multiline);
Console.WriteLine(re2.Matches("1\n22\n333").Count);
// 编译成 IL,启动慢但执行快(适合高频)
var re3 = new Regex(@"\d+", RegexOptions.Compiled);
Console.WriteLine(re3.Match("abc123").Value);
// 单行:. 能匹配换行
var re4 = new Regex(@"a.b", RegexOptions.Singleline);
Console.WriteLine(re4.IsMatch("a\nb"));Output
True
3
123
True超时保护
C#
try
{
// 灾难性回溯的经典模式
var bad = new Regex(@"^(a+)+$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
bad.IsMatch(new string('a', 30) + "b");
}
catch (RegexMatchTimeoutException)
{
Console.WriteLine("正则超时,可能被 ReDoS 攻击");
}Output
正则超时,可能被 ReDoS 攻击Set a timeout on any regex that sees user input. Nested quantifiers like
(a+)+$ backtrack exponentially on crafted input — one request pins a CPU (ReDoS). .NET accepts a TimeSpan in the constructor.常用模式
C#
var patterns = new Dictionary<string, string>
{
["邮箱"] = @"^[\w.+-]+@[\w-]+\.[\w.]+$",
["手机号"] = @"^1[3-9]\d{9}$",
["身份证"] = @"^\d{17}[\dXx]$",
["IPv4"] = @"^((25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d?\d)$",
["URL"] = @"^https?://[\w.-]+(:\d+)?(/[\w./?%&=-]*)?$",
};
foreach (var (name, pattern) in patterns)
{
var re = new Regex(pattern);
Console.WriteLine($"{name}: {re.IsMatch("test@example.com")}");
}Output
邮箱: True
手机号: False
身份证: False
IPv4: False
URL: False基本匹配
C#
using System.Text.RegularExpressions;
var re = new Regex(@"\d{3}-\d{4}");
Console.WriteLine(re.IsMatch("电话 555-1234"));
Console.WriteLine(re.Match("电话 555-1234").Value);
var all = re.Matches("555-1234 和 666-5678");
Console.WriteLine(all.Count);
foreach (Match m in all)
{
Console.WriteLine(m.Value);
}Output
True
555-1234
2
555-1234
666-5678分组
C#
var re = new Regex(@"(\w+)@(\w+)\.com");
var m = re.Match("联系 tom@example.com");
Console.WriteLine(m.Success);
Console.WriteLine(m.Groups[0].Value); // 整体
Console.WriteLine(m.Groups[1].Value); // 第一组
Console.WriteLine(m.Groups[2].Value); // 第二组Output
True
tom@example.com
tom
exampleC#
// 命名分组
var re = new Regex(@"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})");
var m = re.Match("2026-10-31");
Console.WriteLine(m.Groups["year"].Value);
Console.WriteLine(m.Groups["month"].Value);
Console.WriteLine(m.Groups["day"].Value);Output
2026
10
31命名分组比数字索引可靠:正则改了分组顺序,数字索引就全错,命名组不受影响。复杂正则一律用命名组。
替换
C#
var re = new Regex(@"(\d{4})-(\d{2})-(\d{2})");
Console.WriteLine(re.Replace("2026-10-31", "$3/$2/$1"));
// 用委托做复杂替换
var result = re.Replace("2026-10-31", m => $"[{m.Groups[1].Value}年]");
Console.WriteLine(result);
// 去掉 HTML 标签
var tags = new Regex(@"<[^>]+>");
Console.WriteLine(tags.Replace("<p>你好</p>", ""));
// 转义用户输入
Console.WriteLine(Regex.Escape("1+1=2"));Output
31/10/2026
[2026年]
你好
1\+1\=2| 方法 | 返回 |
|---|---|
IsMatch | bool |
Match | 第一个 Match |
Matches | MatchCollection |
Replace | 替换后的字符串 |
Split | 按正则拆分的数组 |
Regex.Escape | 转义特殊字符 |
选项
C#
// 忽略大小写
var re1 = new Regex("hello", RegexOptions.IgnoreCase);
Console.WriteLine(re1.IsMatch("HELLO"));
// 多行:^ 和 $ 匹配每行首尾
var re2 = new Regex(@"^\d+", RegexOptions.Multiline);
Console.WriteLine(re2.Matches("1\n22\n333").Count);
// 编译成 IL,启动慢但执行快(适合高频)
var re3 = new Regex(@"\d+", RegexOptions.Compiled);
Console.WriteLine(re3.Match("abc123").Value);
// 单行:. 能匹配换行
var re4 = new Regex(@"a.b", RegexOptions.Singleline);
Console.WriteLine(re4.IsMatch("a\nb"));Output
True
3
123
True超时保护
C#
try
{
// 灾难性回溯的经典模式
var bad = new Regex(@"^(a+)+$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
bad.IsMatch(new string('a', 30) + "b");
}
catch (RegexMatchTimeoutException)
{
Console.WriteLine("正则超时,可能被 ReDoS 攻击");
}Output
正则超时,可能被 ReDoS 攻击任何处理用户输入的正则都要设超时。
(a+)+$ 这种嵌套量词在特定输入下会指数级回溯,一个请求就能把 CPU 打满(ReDoS)。.NET 允许在构造函数里传 TimeSpan。常用模式
C#
var patterns = new Dictionary<string, string>
{
["邮箱"] = @"^[\w.+-]+@[\w-]+\.[\w.]+$",
["手机号"] = @"^1[3-9]\d{9}$",
["身份证"] = @"^\d{17}[\dXx]$",
["IPv4"] = @"^((25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d?\d)$",
["URL"] = @"^https?://[\w.-]+(:\d+)?(/[\w./?%&=-]*)?$",
};
foreach (var (name, pattern) in patterns)
{
var re = new Regex(pattern);
Console.WriteLine($"{name}: {re.IsMatch("test@example.com")}");
}Output
邮箱: True
手机号: False
身份证: False
IPv4: False
URL: False