介绍
C# 中的正则表达式(Regex)是处理字符串匹配、替换、分割、提取的强大工具。
应用场景
- 验证(手机号、邮箱、身份证);
- 提取(从文本中提取指定格式内容);
- 替换(脱敏、格式化);
- 分割(按复杂规则拆分字符串)。
命名空间
使用正则表达式必须先引用:
using System.Text.RegularExpressions;
正则语法
详见链接:
https://www.malaoshi.top/show_1IX2yvsAZgmZ.html
API
静态方法(快速使用,无需实例化)
| 方法 | 用途 | 示例 |
|---|---|---|
Regex.IsMatch(string input, string pattern) |
判断字符串是否匹配正则 | Regex.IsMatch("123", @"\d+") → true |
Regex.Match(string input, string pattern) |
获取第一个匹配项 | Regex.Match("abc123def456", @"\d+").Value → 123 |
Regex.Matches(string input, string pattern) |
获取所有匹配项(返回 MatchCollection) |
Regex.Matches("abc123def456", @"\d+") → 包含 123、456 |
Regex.Replace(string input, string pattern, string replacement) |
替换匹配的内容 | Regex.Replace("abc123", @"\d+", "xxx") → abcxxx |
Regex.Split(string input, string pattern) |
按正则分割字符串 | Regex.Split("a,b;c", @"[,;]") → ["a","b","c"] |
2. 实例方法(复用正则,性能更优)
当同一正则需多次使用时,建议创建 Regex 实例(避免重复编译):
// 编译正则(RegexOptions.Compiled 提升重复匹配性能)
Regex regex = new Regex(@"\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
// 实例方法调用
bool isMatch = regex.IsMatch("abc123");
Match match = regex.Match("abc123def456");
MatchCollection matches = regex.Matches("abc123def456");
string replaced = regex.Replace("abc123", "xxx");
string[] split = regex.Split("a1b2c");
Match 匹配结果对象
Regex.Match() 方法返回一个 Match 对象,代表第一个匹配项,通过 Match 的属性/方法,获取 匹配的完整信息。
命名空间
System.Text.RegularExpressions
作用
Match 是正则匹配的“最小信息单元”,核心作用是:
- 判断是否匹配成功(
Success); - 获取匹配的字符串内容、位置(
Value/Index); - 提取分组中的结构化数据(
Groups); - 遍历所有匹配项(
NextMatch())。
属性
| 属性 | 说明 | 示例 |
|---|---|---|
Success |
布尔值,标识是否匹配成功(核心!先判断这个再用其他属性) | if (match.Success) { ... } |
Value |
匹配到的完整字符串(最常用) | 正则 \d+ 匹配 "abc123def" → match.Value = "123" |
Length |
匹配到的字符串长度 | 上例中 match.Length = 3 |
Index |
匹配到的字符串在原输入中的起始索引(从0开始) | 上例中 match.Index = 3(”123” 从第4个字符开始) |
Groups |
GroupCollection 类型,包含正则分组的匹配结果 |
正则 (\d{3})(\d{4}) 匹配 "1381234" → Groups[1] = "138",Groups[2] = "1234" |
NextMatch() |
方法,获取原输入中下一个匹配项(替代 Matches() 遍历) |
Match nextMatch = match.NextMatch(); |
例子
获取单个匹配项
using System;
using System.Text.RegularExpressions;
class MatchBasicDemo
{
static void Main()
{
string input = "我的手机号是13812345678,备用号是13998765432";
string pattern = @"1[3-9]\d{9}"; // 手机号正则
// 获取第一个匹配项(返回Match对象)
Match match = Regex.Match(input, pattern);
// 必须先判断Success,避免空值问题
if (match.Success)
{
Console.WriteLine("匹配成功!");
Console.WriteLine($"匹配到的手机号:{match.Value}"); // 13812345678
Console.WriteLine($"起始索引:{match.Index}"); // 7("138..."从第8个字符开始)
Console.WriteLine($"长度:{match.Length}"); // 11(手机号11位)
}
else
{
Console.WriteLine("匹配失败");
}
}
}
分组匹配
正则中的 () 是分组,Match.Groups 可提取分组内容,这是解析结构化字符串的关键:
static void Main()
{
string input = "订单号:ORD20251214001,金额:999元";
// 正则分组:1=订单号前缀,2=订单号日期,3=金额
string pattern = @"订单号:(ORD)(\d{8}\d{3}),金额:(\d+)元";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
// Groups[0] 永远是完整匹配的字符串(整个正则匹配的内容)
Console.WriteLine("完整匹配:" + match.Groups[0].Value); // 订单号:ORD20251214001,金额:999元
// Groups[1]、Groups[2]... 对应正则中的第1、2...个分组
Console.WriteLine("订单前缀:" + match.Groups[1].Value); // ORD
Console.WriteLine("订单编号:" + match.Groups[2].Value); // 20251214001
Console.WriteLine("订单金额:" + match.Groups[3].Value); // 999
}
}
遍历所有匹配项(NextMatch() 方法)
除了 Regex.Matches() 返回集合,也可以用 Match.NextMatch() 逐个获取匹配项:
static void Main()
{
string input = "a1b22c333d4444";
string pattern = @"\d+"; // 匹配所有数字串
Match match = Regex.Match(input, pattern);
// 循环遍历所有匹配项
while (match.Success)
{
Console.WriteLine($"匹配项:{match.Value},索引:{match.Index}");
match = match.NextMatch(); // 获取下一个匹配项
}
}
// 输出:
// 匹配项:1,索引:1
// 匹配项:22,索引:3
// 匹配项:333,索引:6
// 匹配项:4444,索引:10
关键注意事项
Groups[0]是默认分组:Groups[0]等价于Match.Value,代表整个正则匹配的内容,自定义分组从Groups[1]开始;- 未匹配的分组:如果分组未匹配到内容(如正则
(abc)?\d+匹配"123"),Groups[1].Success = false,Value = ""; - 性能建议:遍历大量匹配项时,
NextMatch()和Matches()性能接近,但NextMatch()适合“按需获取”(比如找到第N个匹配项就停止); - 空值安全:永远先判断
match.Success,再访问Value/Groups,否则即使匹配失败,Value也会返回空字符串(不会抛异常,但逻辑易出错)。
MatchCollection
Regex.Matches(input, pattern) 方法返回 MatchCollection,只读集合类,专门用于存储多个正则匹配结果——集合中的每一个元素都是 Match 对象(代表单个匹配项)。
包含输入字符串中所有符合正则规则的匹配项。通过遍历 MatchCollection,获取所有匹配结果的细节(如内容、位置、分组等);
是一个延迟加载的集合(默认情况下),遍历到某一项时才会实际执行匹配,内存效率较高。
作用
- 通过
Regex.Matches(input, pattern)获取集合; - 用
foreach遍历集合,逐个处理Match对象; - 结合
Match.Groups提取分组数据; - 大数据量场景优先用
Match.NextMatch()按需匹配,小数据量/需全量访问时用MatchCollection。
掌握 MatchCollection 的遍历和特性,能高效处理“提取所有符合规则内容”的场景(如批量提取手机号、邮箱、订单号等)。
命名空间下
System.Text.RegularExpressions
特性
| 特性 | 说明 |
|---|---|
| 元素类型 | 每个元素都是 Match 对象(可访问 Value/Groups/Index 等) |
| 延迟加载 | 默认不一次性匹配所有结果,遍历到第 N 项时才匹配第 N 项(节省内存) |
| 只读性 | 无法手动添加/删除元素,仅能通过正则匹配生成 |
| 核心属性 | Count:集合中匹配项的总数(调用时会触发全量匹配);IsReadOnly:固定为 true |
例子
基础遍历(获取所有匹配项)
最常用场景:提取字符串中所有符合规则的内容(如所有数字、所有邮箱)。
using System;
using System.Text.RegularExpressions;
using System.Linq; // 用于 Cast<Match>()
class MatchCollectionDemo
{
static void Main()
{
string input = "张三:13812345678,李四:13998765432,王五:18800009999";
string pattern = @"1[3-9]\d{9}"; // 手机号正则
// 获取所有匹配项的集合
MatchCollection matches = Regex.Matches(input, pattern);
// 方式1:foreach 遍历(推荐,延迟加载,内存友好)
Console.WriteLine("foreach 遍历结果:");
foreach (Match match in matches)
{
if (match.Success) // 理论上正则匹配的项 Success 都是 true
{
Console.WriteLine($"手机号:{match.Value},起始索引:{match.Index}");
}
}
// 方式2:转换为 List<Match> 遍历(需全量匹配,适合需要随机访问的场景)
Console.WriteLine("\n转换为 List 遍历结果:");
List<Match> matchList = matches.Cast<Match>().ToList();
for (int i = 0; i < matchList.Count; i++)
{
Console.WriteLine($"第{i+1}个手机号:{matchList[i].Value}");
}
}
}
// 输出:
// foreach 遍历结果:
// 手机号:13812345678,起始索引:4
// 手机号:13998765432,起始索引:15
// 手机号:18800009999,起始索引:26
//
// 转换为 List 遍历结果:
// 第1个手机号:13812345678
// 第2个手机号:13998765432
// 第3个手机号:18800009999
结合分组提取结构化数据
遍历 MatchCollection 时,可通过每个 Match 的 Groups 提取分组内容(如解析多个订单信息)。
static void Main()
{
string input = "订单1:ORD20250101-100元;订单2:ORD20250102-200元;订单3:ORD20250103-300元";
// 正则分组:1=订单号,2=金额
string pattern = @"ORD(\d{8})-(\d+)元";
MatchCollection matches = Regex.Matches(input, pattern);
foreach (Match match in matches)
{
string orderNo = match.Groups[1].Value; // 分组1:订单日期编号
string amount = match.Groups[2].Value; // 分组2:金额
Console.WriteLine($"订单编号:{orderNo},金额:{amount}元");
}
}
// 输出:
// 订单编号:20250101,金额:100元
// 订单编号:20250102,金额:200元
// 订单编号:20250103,金额:300元
获取匹配项总数(Count 属性)
Count 属性会触发全量匹配(即使是延迟加载的集合),适合需要先知道匹配项数量的场景:
string input = "a1b22c333d4444";
MatchCollection matches = Regex.Matches(input, @"\d+");
Console.WriteLine($"匹配项总数:{matches.Count}"); // 输出:4
注意事项
1. 延迟加载 vs 立即加载
- 默认行为:
MatchCollection是延迟加载的(Regex.Matches返回的是RegexMatchCollection,继承自MatchCollection),遍历到第 N 项时才执行第 N 次匹配,适合大数据量场景; - 强制立即加载:调用
matches.Count或matches.Cast<Match>().ToList()会触发全量匹配,一次性生成所有Match对象。
空集合处理
如果正则未匹配到任何内容,MatchCollection 不为 null,但 Count = 0,遍历不会执行:
string input = "abcdef";
MatchCollection matches = Regex.Matches(input, @"\d+");
Console.WriteLine(matches == null); // false
Console.WriteLine(matches.Count); // 0
类型转换
MatchCollection 是非泛型集合,如需使用 LINQ 方法(如 Where/Select),需先转换为泛型集合:
// 提取所有长度 > 2 的数字串
List<string> result = matches.Cast<Match>()
.Where(m => m.Value.Length > 2)
.Select(m => m.Value)
.ToList();
Match vs MatchCollection
Match 和 MatchCollection 是配套使用的核心类型,前者代表单个匹配结果,后者代表多个匹配结果的集合。二者的核心差异、适用场景和使用方式如下,结合对比和示例能快速理清区别:
核心定义与定位
| 类型 | 核心定义 | 生成方式 | 元素/内容 |
|---|---|---|---|
Match |
表示单个正则匹配项的所有细节(内容、位置、分组等) | 1. Regex.Match(input, pattern)(返回第一个匹配项)<br>2. Match.NextMatch()(获取下一个匹配项)<br>3. 遍历 MatchCollection 时的单个元素 |
单个匹配结果:<br>- Value(匹配字符串)<br>- Index(起始位置)<br>- Groups(分组数据)<br>- Success(是否匹配成功) |
MatchCollection |
存储所有正则匹配项的只读集合(延迟加载) | Regex.Matches(input, pattern)(返回所有匹配项的集合) |
多个 Match 对象:<br>- Count(匹配项总数)<br>- 遍历可获取每个 Match 元素 |
核心差异对比
| 维度 | Match |
MatchCollection |
|---|---|---|
| 数量 | 单个匹配项(1个) | 多个匹配项(0/1/N个) |
| 加载方式 | 即时加载(获取即匹配) | 默认延迟加载(遍历到某一项才匹配,调用 Count 会触发全量匹配) |
| 访问方式 | 直接访问属性(Value/Groups) |
需遍历(foreach)或转换为泛型集合后访问 |
| 内存占用 | 极低(仅存储单个匹配信息) | 全量加载时较高(存储所有匹配项),延迟加载时与 Match 相当 |
| 适用操作 | 1. 获取第一个匹配项<br>2. 按需逐次匹配(找到目标即停止) | 1. 遍历所有匹配项<br>2. 需知道匹配项总数<br>3. 随机访问(转换为 List 后) |
典型使用场景 & 示例
场景1:只需要第一个匹配项 → 用 Match
string input = "手机号:13812345678,备用号:13998765432";
string pattern = @"1[3-9]\d{9}";
// 仅获取第一个匹配项,用 Match 更高效
Match firstMatch = Regex.Match(input, pattern);
if (firstMatch.Success)
{
Console.WriteLine("第一个手机号:" + firstMatch.Value); // 13812345678
}
场景2:需要遍历所有匹配项 → 用 MatchCollection
string input = "a1b22c333d4444";
string pattern = @"\d+";
// 获取所有匹配项,用 MatchCollection 遍历
MatchCollection allMatches = Regex.Matches(input, pattern);
Console.WriteLine($"匹配项总数:{allMatches.Count}"); // 4
foreach (Match match in allMatches)
{
Console.WriteLine($"匹配项:{match.Value},索引:{match.Index}");
}
场景3:大数据量,按需匹配(找到目标即停止) → 用 Match.NextMatch()
MatchCollection 调用 Count 会全量匹配,大数据量下效率低;而 Match.NextMatch() 逐次匹配,找到目标即可停止:
string input = "海量文本...123...456...789..."; // 假设输入是10万字符的大文本
string pattern = @"\d{3}";
Match match = Regex.Match(input, pattern);
// 只找第三个匹配项,找到即停止
int count = 0;
while (match.Success)
{
count++;
if (count == 3)
{
Console.WriteLine("第三个3位数字:" + match.Value); // 789
break; // 无需匹配剩余内容,节省性能
}
match = match.NextMatch();
}
场景4:需要随机访问匹配项 → MatchCollection 转 List
string input = "a1b22c333d4444";
MatchCollection matches = Regex.Matches(input, @"\d+");
// 转换为 List<Match>,支持索引访问
List<Match> matchList = matches.Cast<Match>().ToList();
Console.WriteLine("第二个匹配项:" + matchList[1].Value); // 22
Console.WriteLine("最后一个匹配项:" + matchList.Last().Value); // 4444
性能选择指南
| 场景 | 推荐选择 | 原因 |
|---|---|---|
| 仅需第一个匹配项 | Match(Regex.Match()) |
无需遍历,直接获取,性能最优 |
| 小数据量,需遍历所有匹配项 | MatchCollection(Regex.Matches()) |
代码简洁,foreach 直接遍历 |
| 大数据量,需遍历所有匹配项 | Match.NextMatch() |
延迟逐次加载,内存占用低 |
| 需随机访问(如取第N个匹配项) | MatchCollection 转 List<Match> |
索引访问更便捷(注意:大数据量慎用) |
| 需知道匹配项总数 | MatchCollection(调用 Count) |
直接获取总数,无需手动计数 |
避坑要点
MatchCollection不是 null:即使无匹配项,Regex.Matches()返回的集合也不为null,只是Count = 0;Match.Success必判:使用Match前必须先判断Success,否则空匹配的Value为空字符串,易导致逻辑错误;延迟加载的坑:
MatchCollection遍历多次会重复执行匹配(每次遍历都重新匹配),如需多次遍历,建议先转List<Match>缓存;// 错误:多次遍历 MatchCollection 会重复匹配 foreach (Match m in matches) { ... } foreach (Match m in matches) { ... } // 再次执行正则匹配 // 正确:转 List 后缓存,仅匹配一次 var matchList = matches.Cast<Match>().ToList(); foreach (Match m in matchList) { ... } foreach (Match m in matchList) { ... }
常用正则选项
| 选项 | 说明 |
|---|---|
IgnoreCase |
忽略大小写匹配 |
Multiline |
多行模式(^/$ 匹配每行开头/结尾,而非整个字符串) |
Singleline |
单行模式(. 匹配包括换行在内的所有字符) |
Compiled |
编译正则表达式为程序集(提升重复匹配性能,初始化稍慢) |
IgnorePatternWhitespace |
忽略正则中的空白符(便于编写易读的正则) |
示例:多行模式匹配每行开头的数字
string text = "1. 张三\n2. 李四\n3. 王五";
// 多行模式:^ 匹配每行开头
MatchCollection matches = Regex.Matches(text, @"^\d+", RegexOptions.Multiline);
matches.Cast<Match>().Select(m => m.Value).ToList().ForEach(Console.WriteLine); // 1、2、3
例子
简单例子
//被查找的字符串
string origin = "abc<div>iphone8 iphoneX iphone12</div>123";
//实例化Regex,参数为我们的正则表达式
Regex regex = new Regex("<div>.*</div>");
//生成Match对象
Match match = regex.Match(origin);
验证手机号
/// <summary>
/// 验证中国大陆手机号(11位,以1开头)
/// </summary>
static bool IsPhoneNumber(string phone)
{
// 正则:^1 开头,后跟10位数字,$ 结尾
string pattern = @"^1[3-9]\d{9}$";
return Regex.IsMatch(phone, pattern);
}
// 测试
Console.WriteLine(IsPhoneNumber("13812345678")); // true
Console.WriteLine(IsPhoneNumber("12345678901")); // false(第二位非3-9)
分组提取-解析URL
/// <summary>
/// 解析URL,提取协议、域名、路径
/// </summary>
static void ParseUrl(string url)
{
// 分组:协议、域名、路径
string pattern = @"^(https?):\/\/([^\/]+)(\/.*)?$";
Match match = Regex.Match(url, pattern);
if (match.Success)
{
Console.WriteLine("协议:" + match.Groups[1].Value); // https
Console.WriteLine("域名:" + match.Groups[2].Value); // www.baidu.com
Console.WriteLine("路径:" + (match.Groups[3].Success ? match.Groups[3].Value : "/")); // /index.html
}
}
// 测试
ParseUrl("https://www.baidu.com/index.html");
提取匹配内容-提取所有邮箱
/// <summary>
/// 从文本中提取所有邮箱地址
/// </summary>
static List<string> ExtractEmails(string text)
{
// 邮箱正则:用户名@域名(简化版)
string pattern = @"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";
MatchCollection matches = Regex.Matches(text, pattern);
return matches.Cast<Match>().Select(m => m.Value).ToList();
}
// 测试
string text = "我的邮箱是test@example.com,备用邮箱是abc_123@qq.com";
List<string> emails = ExtractEmails(text);
emails.ForEach(Console.WriteLine); // 输出:test@example.com、abc_123@qq.com
替换匹配内容-脱敏手机号
/// <summary>
/// 手机号脱敏(中间4位替换为****)
/// </summary>
static string DesensitizePhone(string phone)
{
// 分组匹配:第1组(前3位)、第2组(中间4位)、第3组(后4位)
string pattern = @"(\d{3})(\d{4})(\d{4})";
// 替换为:第1组 + **** + 第3组($1/$2/$3 引用分组)
return Regex.Replace(phone, pattern, "$1****$3");
}
// 测试
Console.WriteLine(DesensitizePhone("13812345678")); // 138****5678
分割字符串-按任意空白符分割
string str = "a b\tc\nd";
// 按任意空白符(空格、制表符、换行)分割
string[] parts = Regex.Split(str, @"\s+");
parts.ForEach(Console.WriteLine); // 输出:a、b、c、d
性能优化
- 复用 Regex 实例:同一正则多次使用时,创建
Regex实例并指定RegexOptions.Compiled(避免重复编译); - 避免贪婪匹配滥用:
.*是贪婪匹配(尽可能多匹配),非必要时用.*?(非贪婪,尽可能少匹配);
示例:"<a>1</a><a>2</a>",/<a>.*<\/a>/匹配整个字符串,/<a>.*?<\/a>/匹配<a>1</a>; - 限定匹配范围:如手机号验证用
^1[3-9]\d{9}$(首尾锚定),避免部分匹配。
使用注意
- 转义字符:C# 字符串中
\是转义符,正则中的\d需写成@"\d"(逐字字符串)或\\d; - 空值处理:匹配前检查输入是否为
null,避免ArgumentNullException;
参考:
https://blog.csdn.net/wangjiangrong/article/details/111179202