C# 正则表达式

介绍

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 是正则匹配的“最小信息单元”,核心作用是:

  1. 判断是否匹配成功(Success);
  2. 获取匹配的字符串内容、位置(Value/Index);
  3. 提取分组中的结构化数据(Groups);
  4. 遍历所有匹配项(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

关键注意事项

  1. Groups[0] 是默认分组:Groups[0] 等价于 Match.Value,代表整个正则匹配的内容,自定义分组从 Groups[1] 开始;
  2. 未匹配的分组:如果分组未匹配到内容(如正则 (abc)?\d+ 匹配 "123"),Groups[1].Success = false,Value = "";
  3. 性能建议:遍历大量匹配项时,NextMatch() 和 Matches() 性能接近,但 NextMatch() 适合“按需获取”(比如找到第N个匹配项就停止);
  4. 空值安全:永远先判断 match.Success,再访问 Value/Groups,否则即使匹配失败,Value 也会返回空字符串(不会抛异常,但逻辑易出错)。

MatchCollection

Regex.Matches(input, pattern) 方法返回 MatchCollection,只读集合类,专门用于存储多个正则匹配结果——集合中的每一个元素都是 Match 对象(代表单个匹配项)。

包含输入字符串中所有符合正则规则的匹配项。通过遍历 MatchCollection,获取所有匹配结果的细节(如内容、位置、分组等);

是一个延迟加载的集合(默认情况下),遍历到某一项时才会实际执行匹配,内存效率较高。

作用

  1. 通过 Regex.Matches(input, pattern) 获取集合;
  2. 用 foreach 遍历集合,逐个处理 Match 对象;
  3. 结合 Match.Groups 提取分组数据;
  4. 大数据量场景优先用 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) 直接获取总数,无需手动计数

避坑要点

  1. MatchCollection 不是 null:即使无匹配项,Regex.Matches() 返回的集合也不为 null,只是 Count = 0;
  2. Match.Success 必判:使用 Match 前必须先判断 Success,否则空匹配的 Value 为空字符串,易导致逻辑错误;
  3. 延迟加载的坑: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