C#正则表达式全面详解:从基础到高级应用

C#正则表达式全面详解:从基础到高级应用

正则表达式是处理文本模式匹配的强大工具,在数据验证、文本提取、格式转换等场景中发挥着重要作用。C# 通过System.Text.RegularExpressions命名空间提供了完整的正则表达式支持,本文将全面介绍 C# 中正则表达式的使用方法,从基础语法到高级特性,帮助开发者掌握这一实用技术。

一、正则表达式基础

1. 什么是正则表达式

正则表达式(Regular Expression)是一种用于描述文本模式的字符串,它可以精确地定义需要匹配的文本格式。在 C# 中,正则表达式通常用于:

  • 数据验证(如邮箱、手机号、身份证号)
  • 文本搜索与提取(如从日志中提取特定信息)
  • 文本替换与格式化(如批量修改文本格式)
  • 复杂字符串拆分

2. C# 正则表达式核心类

C# 中处理正则表达式的核心类位于System.Text.RegularExpressions命名空间:

  • Regex:正则表达式的主要类,提供匹配、替换、拆分等操作
  • Match:表示单个匹配结果
  • MatchCollection:表示多个匹配结果的集合
  • GroupCollection:表示匹配中的分组集合
  • CaptureCollection:表示捕获的子字符串集合

使用前需引入命名空间:

using System.Text.RegularExpressions;

二、Regex 类的基本用法

1. 验证匹配(IsMatch 方法)

Regex.IsMatch方法用于检查输入字符串是否与正则表达式匹配,返回布尔值:

// 基本用法
string pattern = @"^d{4}-d{2}-d{2}$"; // 日期格式:yyyy-MM-dd
string input1 = "2023-10-05";
string input2 = "2023/10/05";
bool isMatch1 = Regex.IsMatch(input1, pattern);
bool isMatch2 = Regex.IsMatch(input2, pattern);
Console.WriteLine($"input1匹配结果:{isMatch1}"); // True
Console.WriteLine($"input2匹配结果:{isMatch2}"); // False

// 带选项的匹配
// 忽略大小写
bool caseInsensitiveMatch = Regex.IsMatch("Hello", @"hello", RegexOptions.IgnoreCase);
Console.WriteLine($"忽略大小写匹配:{caseInsensitiveMatch}"); // True

2. 单次匹配(Match 方法)

Regex.Match方法返回输入字符串中与正则表达式匹配的第一个结果(Match 对象):

string text = "邮箱:user@example.com,电话:13800138000";
string emailPattern = @"w+@w+.w+"; // 简单的邮箱正则
Match match = Regex.Match(text, emailPattern);

if (match.Success)
{
   Console.WriteLine($"找到匹配:{match.Value}"); // 输出:user@example.com
   Console.WriteLine($"匹配位置:{match.Index}"); // 输出:3(从0开始的索引)
   Console.WriteLine($"匹配长度:{match.Length}"); // 输出:16
}
else
{
   Console.WriteLine("未找到匹配");
}

3. 多次匹配(Matches 方法)

Regex.Matches方法返回输入字符串中所有与正则表达式匹配的结果(MatchCollection 集合):

string text = "订单号:ORD-12345,订单号:ORD-67890,订单号:ORD-54321";
string orderPattern = @"ORD-d{5}"; // 匹配ORD-后跟5位数字

MatchCollection matches = Regex.Matches(text, orderPattern);
Console.WriteLine($"找到{matches.Count}个匹配:");

foreach (Match m in matches)
{
   Console.WriteLine(m.Value);
}


// 输出:
// ORD-12345
// ORD-67890
// ORD-54321

三、正则表达式语法基础

C# 正则表达式语法基于标准正则表达式,并支持一些.NET 特定扩展。以下是常用语法元素:

1. 字符匹配

语法说明示例匹配
普通字符匹配自身abc“abc”
.匹配任意单个字符(除换行符)a.c“abc”、“a1c”
d匹配数字(0-9)d{3}“123”、“456”
D匹配非数字D“a”、“#”、" "
w匹配字母、数字或下划线w+“name”、“user123”
W匹配非单词字符W“@”、“#”、" "
s匹配空白字符(空格、制表符等)asb“a b”、“atb”
S匹配非空白字符S+“text”、“123”

2. 重复匹配

语法说明示例匹配
*匹配前面的元素 0 次或多次ab*“a”、“ab”、“abb”
+匹配前面的元素 1 次或多次ab+“ab”、“abb”
?匹配前面的元素 0 次或 1 次ab?“a”、“ab”
{n}匹配前面的元素恰好 n 次a{2}“aa”
{n,}匹配前面的元素至少 n 次a{2,}“aa”、“aaa”
{n,m}匹配前面的元素至少 n 次,最多 m 次a{2,3}“aa”、“aaa”

3. 定位符

语法说明示例匹配
^匹配字符串的开始^abc“abc” 在字符串开头
$匹配字符串的结束abc$“abc” 在字符串结尾
b匹配单词边界bwordb“word” 作为独立单词
B匹配非单词边界BwordB“word” 在单词中间

4. 字符类

语法说明示例匹配
[abc]匹配 a、b 或 c[abc]“a”、“b”、“c”
[^abc]匹配除 a、b、c 外的任意字符[^abc]“d”、“1”、“@”
[a-z]匹配 a 到 z 的任意小写字母[a-z]“a”、“m”、“z”
[A-Z]匹配 A 到 Z 的任意大写字母[A-Z]“A”、“M”、“Z”
[0-9]匹配 0 到 9 的任意数字(等同于 d)[0-9]“0”、“5”、“9”
[a-zA-Z0-9]匹配字母或数字(类似 w)[a-zA-Z0-9]“a”、“B”、“3”

5. 逻辑运算符

语法说明示例匹配
``逻辑或`ab
()分组(ab)+“ab”、“abab”
(?:)非捕获分组(仅用于分组,不捕获结果)(?:ab)+同上,但不捕获分组

四、分组与捕获

分组是正则表达式中非常强大的功能,允许我们从匹配的文本中提取特定部分。

1. 基本分组捕获

string text = "张三,25,男;李四,30,女";

// 匹配姓名、年龄、性别,使用逗号分隔
string pattern = @"(w+),(d+),(w+)";

MatchCollection matches = Regex.Matches(text, pattern);
foreach (Match m in matches)
{
   Console.WriteLine($"完整匹配:{m.Value}");

   // 遍历捕获的分组(Groups[0]是整个匹配,从1开始是实际分组)
   for (int i = 1; i < m.Groups.Count; i++)
   {
       Console.WriteLine($"分组{i}{m.Groups[i].Value}");
   }

   Console.WriteLine("---");
}


// 输出:
// 完整匹配:张三,25,男
// 分组1:张三
// 分组2:25
// 分组3:男
// ---
// 完整匹配:李四,30,女
// 分组1:李四
// 分组2:30
// 分组3:女
// ---

2. 命名分组

使用(?<name>...)语法可以创建命名分组,提高代码可读性:

string text = "订单:ORD-12345,金额:99.99元,日期:2023-10-05";

// 命名分组:orderId、amount、date
string pattern = @"订单:(?<orderId>ORD-d+),金额:(?<amount>d+.d+)元,日期:(?<date>d{4}-d{2}-d{2})";

Match match = Regex.Match(text, pattern);
if (match.Success)
{
   // 通过分组名访问
   Console.WriteLine($"订单号:{match.Groups["orderId"].Value}");
   Console.WriteLine($"金额:{match.Groups["amount"].Value}");
   Console.WriteLine($"日期:{match.Groups["date"].Value}");
}


// 输出:
// 订单号:ORD-12345
// 金额:99.99
// 日期:2023-10-05

3. 非捕获分组

使用(?:...)创建非捕获分组,仅用于分组逻辑,不保存捕获结果:

string text = "cat cats dog dogs";

// 非捕获分组(?:s)?表示可选的s
string pattern = @"(cat|dog)(?:s)?";
foreach (Match m in Regex.Matches(text, pattern))
{
   Console.WriteLine($"匹配:{m.Value},分组1:{m.Groups[1].Value}");
}


// 输出:
// 匹配:cat,分组1:cat
// 匹配:cats,分组1:cat
// 匹配:dog,分组1:dog
// 匹配:dogs,分组1:dog

4. 捕获集合

当分组包含重复量词时,Captures属性可以获取所有中间捕获结果:

string text = "aaaabbb";

// 匹配连续的a和连续的b
string pattern = @"(w+?)1+";
Match match = Regex.Match(text, pattern);
if (match.Success)
{
   Console.WriteLine($"完整匹配:{match.Value}");
   Console.WriteLine($"分组1的值:{match.Groups[1].Value}");
   Console.WriteLine("所有捕获:");

   foreach (Capture capture in match.Groups[1].Captures)
   {
       Console.WriteLine($"  {capture.Value} at {capture.Index}");
   }
}


// 输出:
// 完整匹配:aaaa
// 分组1的值:a
// 所有捕获:
//   a at 0
//   a at 1
//   a at 2
//   a at 3

五、替换操作

正则表达式的替换功能可以基于模式匹配来修改文本,非常适合批量处理文本。

1. 基本替换(Replace 方法)

string text = "手机号:13800138000,另一个手机号:13900139000";

// 匹配手机号,将中间4位替换为*
string pattern = @"(d{3})d{4}(d{4})";
string replacement = "$1****$2"; // $1表示第一个分组,$2表示第二个分组
string result = Regex.Replace(text, pattern, replacement);

Console.WriteLine(result);
// 输出:手机号:138****8000,另一个手机号:139****9000

2. 带回调的替换

Regex.Replace方法可以接受一个MatchEvaluator委托,实现复杂的替换逻辑:

string text = "价格:100,折扣后:80;价格:200,折扣后:150";

// 匹配价格数字
string pattern = @"d+";
// 使用回调函数将价格增加10%
string result = Regex.Replace(text, pattern, match =>
{
   if (int.TryParse(match.Value, out int price))
   {
       return (price * 1.1).ToString("0.00");
   }

   return match.Value;
});

Console.WriteLine(result);

// 输出:价格:110.00,折扣后:88.00;价格:220.00,折扣后:165.00

3. 条件替换

结合分组可以实现条件替换逻辑:

string text = "张三:男,25;李四:女,30;王五:男,35";

// 匹配姓名:性别,年龄
string pattern = @"(?<name>w+):(?<gender>男|女),(?<age>d+)";

// 根据性别添加不同的前缀
string result = Regex.Replace(text, pattern, match =>
{
   string gender = match.Groups["gender"].Value;
   string prefix = gender == "男" ? "先生" : "女士";
   return $"{match.Groups["name"].Value}{prefix}({match.Groups["age"].Value}岁)";
});

Console.WriteLine(result);

// 输出:张三先生(25岁);李四女士(30岁);王五先生(35岁)

六、拆分操作

Regex.Split方法可以根据正则表达式匹配来拆分字符串,比string.Split更灵活。

1. 基本拆分

string text = "苹果,香蕉;橙子|葡萄";

// 匹配逗号、分号或竖线作为分隔符
string pattern = @"[,;|]";
string[] fruits = Regex.Split(text, pattern);

Console.WriteLine("拆分结果:");

foreach (string fruit in fruits)
{
   Console.WriteLine(fruit);
}


// 输出:
// 苹果
// 香蕉
// 橙子
// 葡萄

2. 保留分隔符

通过分组可以在拆分结果中保留分隔符:

string text = "第一章 介绍n1.1 背景n1.2 目的n第二章 方法n2.1 步骤";

// 匹配章节标题(数字+章)或小节标题(数字.数字)
string pattern = @"(d+章)|(d+.d+)";
string[] parts = Regex.Split(text, pattern);

Console.WriteLine("拆分结果:");
foreach (string part in parts)
{
   if (!string.IsNullOrEmpty(part))
   {
       Console.WriteLine($"[{part}]");
   }
}


// 输出:
// [第一章]
//  介绍
//
// [1.1]
//  背景
//
// [1.2]
//  目的
//
// [第二章]
//  方法
//
// [2.1]
//  步骤

七、正则表达式选项

RegexOptions枚举提供了多种选项来控制正则表达式的行为:

// 常用的RegexOptions选项
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Multiline;

// 1. IgnoreCase:忽略大小写
bool caseInsensitive = Regex.IsMatch("Hello", "hello", RegexOptions.IgnoreCase);

// 2. Multiline:多行模式,^匹配每行开头,$匹配每行结尾
string multiLineText = "line1nLine2nLINE3";
MatchCollection multiLineMatches = Regex.Matches(multiLineText, @"^lined", RegexOptions.IgnoreCase | RegexOptions.Multiline);

// 3. Singleline:单行模式,.匹配包括换行符在内的所有字符
string singleLineText = "first linensecond line";
bool singleLineMatch = Regex.IsMatch(singleLineText, @"first.*second", RegexOptions.Singleline);

// 4. Compiled:编译正则表达式为IL代码,提高多次使用的性能
Regex compiledRegex = new Regex(@"d+", RegexOptions.Compiled);

// 5. IgnorePatternWhitespace:忽略模式中的空白,允许添加注释
string patternWithComments = @"
   d{3}    # 区号
   -?       # 可选的连字符
   d{8}    # 8位号码
";


bool phoneMatch = Regex.IsMatch("010-12345678", patternWithComments, RegexOptions.IgnorePatternWhitespace);

各选项的主要作用:

选项作用
IgnoreCase忽略大小写
Multiline多行模式,^$匹配每行的开头和结尾
Singleline单行模式,.匹配所有字符(包括换行符)
Compiled编译正则表达式,提高重复使用的性能
IgnorePatternWhitespace忽略模式中的空白,允许注释(#后的内容)
RightToLeft从右向左搜索
ExplicitCapture只捕获显式命名的分组

八、性能优化与最佳实践

1. 编译正则表达式

对于频繁使用的正则表达式,使用RegexOptions.Compiled可以显著提高性能:

// 不编译的正则表达式(解释执行)
var regex1 = new Regex(@"d{3}-d{8}");

// 编译的正则表达式(转换为IL代码)
var regex2 = new Regex(@"d{3}-d{8}", RegexOptions.Compiled);

// 性能测试
string testText = "电话:010-12345678,手机:13800138000";

Stopwatch stopwatch = new Stopwatch();

// 测试非编译版本
stopwatch.Start();
for (int i = 0; i < 100000; i++)
{
   regex1.IsMatch(testText);
}

stopwatch.Stop();

Console.WriteLine($"非编译版本:{stopwatch.ElapsedMilliseconds}ms");


// 测试编译版本
stopwatch.Restart();
for (int i = 0; i < 100000; i++)
{
   regex2.IsMatch(testText);
}


stopwatch.Stop();


Console.WriteLine($"编译版本:{stopwatch.ElapsedMilliseconds}ms");

注意:编译正则表达式会增加初始化时间和内存占用,适合重复使用的场景

2. 缓存正则表达式

对于重复使用的正则表达式,应避免重复创建Regex对象:

// 不好的做法:每次调用都创建新的Regex对象
bool IsValidEmailBad(string email)
{
   return Regex.IsMatch(email, @"^[w-]+(.[w-]+)*@([w-]+.)+[a-zA-Z]{2,7}$");
}


// 好的做法:缓存Regex对象
private static readonly Regex _emailRegex = new Regex(
   @"^[w-]+(.[w-]+)*@([w-]+.)+[a-zA-Z]{2,7}$",
   RegexOptions.Compiled);


bool IsValidEmailGood(string email)
{
   return _emailRegex.IsMatch(email);
}

3. 设置超时时间

防止恶意或复杂的正则表达式导致性能问题:

try
{
   // 设置超时时间为1秒
   var regex = new Regex(@"^(d+)+$", RegexOptions.None, TimeSpan.FromSeconds(1));

   // 这种模式对"12345678901234567890a"等输入可能导致长时间匹配
   bool isMatch = regex.IsMatch("12345678901234567890a");
}
catch (RegexMatchTimeoutException)
{
   Console.WriteLine("匹配超时");
}

4. 避免过度复杂的正则表达式

复杂的正则表达式难以维护且性能可能不佳,可考虑:

  • 将复杂模式拆分为多个简单正则表达式
  • 结合代码逻辑处理,而非完全依赖正则表达式
  • 使用命名分组提高可读性

九、常用正则表达式示例

1. 数据验证

// 1. 邮箱验证
string emailPattern = @"^[w-]+(.[w-]+)*@([w-]+.)+[a-zA-Z]{2,7}$";
bool IsValidEmail(string email) => Regex.IsMatch(email, emailPattern);


// 2. 手机号验证(中国大陆)
string phonePattern = @"^1[3-9]d{9}$";
bool IsValidPhone(string phone) => Regex.IsMatch(phone, phonePattern);


// 3. 身份证号验证(18位)
string idCardPattern = @"^d{6}(18|19|20)d{2}(0[1-9]|1[0-2])(0[1-9]|[12]d|3[01])d{3}[dXx]$";
bool IsValidIdCard(string id) => Regex.IsMatch(id, idCardPattern);


// 4. URL验证
string urlPattern = @"^(https?://)?([da-z.-]+).([a-z.]{2,6})([/w .-]*)*/?$";
bool IsValidUrl(string url) => Regex.IsMatch(url, urlPattern);


// 5. 密码强度验证(至少8位,包含大小写字母、数字和特殊字符)
string passwordPattern = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[^da-zA-Z]).{8,}$";
bool IsStrongPassword(string password) => Regex.IsMatch(password, passwordPattern);

2. 文本提取

// 1. 从HTML中提取所有链接
string html = @"<a href='https://example.com'>示例</a><a href='https://test.com'>测试</a>";
string linkPattern = @"href=['""]([^'""]+)['""]";


MatchCollection linkMatches = Regex.Matches(html, linkPattern);
foreach (Match m in linkMatches)
{
   Console.WriteLine("链接:" + m.Groups[1].Value);
}


// 2. 从日志中提取错误信息
string logText = @"2023-10-05 10:00: INFO 启动成功
2023-10-05 10:01: ERROR 连接失败:无法连接到数据库
2023-10-05 10:02: INFO 重试连接
2023-10-05 10:03: ERROR 连接失败:超时";

string errorPattern = @"(d{4}-d{2}-d{2} d{2}:d{2}): ERROR (.*)";
foreach (Match m in Regex.Matches(logText, errorPattern))
{
   Console.WriteLine($"[{m.Groups[1].Value}] 错误:{m.Groups[2].Value}");
}


// 3. 提取字符串中的所有数字
string textWithNumbers = "订单1:金额100元,订单2:金额200元";
MatchCollection numberMatches = Regex.Matches(textWithNumbers, @"d+");
foreach (Match m in numberMatches)
{
   Console.WriteLine("数字:" + m.Value);
}

3. 文本格式化

// 1. 格式化电话号码
string phone = "01012345678";
string formattedPhone = Regex.Replace(phone, @"^(d{3})(d{8})$", "$1-$2");
// 结果:010-12345678


// 2. 格式化日期(将20231005转换为2023-10-05)
string date = "20231005";
string formattedDate = Regex.Replace(date, @"^(d{4})(d{2})(d{2})$", "$1-$2-$3");
// 结果:2023-10-05


// 3. 驼峰命名转下划线命名
string camelCase = "userNameAgeEmail";
string snakeCase = Regex.Replace(camelCase, @"([A-Z])", "_$1").ToLower();
// 结果:user_name_age_email

十、高级特性

1. 平衡组(匹配嵌套结构)

C# 正则表达式支持平衡组,可用于匹配嵌套结构(如括号、HTML 标签等):

// 匹配嵌套的括号
string nestedText = "a(b(c)d)e(f)g";
string balancedPattern = @"
   (                  # 匹配左括号
   (                   # 开始捕获内容
       (?>             # 非回溯组
           [^()]+     # 匹配非括号字符
           |           # 或者
           (?<Open> ( )  # 遇到左括号,Open计数器+1
           |           # 或者
           (?<-Open> ) ) # 遇到右括号,Open计数器-1
       )*              # 重复
       (?(Open)        # 如果Open计数器>0(有未闭合的括号)
           (?!)       # 不匹配
           |           # 否则
           )           # 匹配右括号
   )
";


Match match = Regex.Match(nestedText, balancedPattern, RegexOptions.IgnorePatternWhitespace);
if (match.Success)
{
   Console.WriteLine("匹配的嵌套结构:" + match.Value); // 输出:(b(c)d)

}

2. 条件匹配

基于前面的分组是否匹配来决定后续匹配:

// 条件匹配:如果有区号则必须有连字符
string pattern = @"(?:(d{3})-)?d{8}|(?(1)d{8})";

// 解释:
// (?:(d{3})-)? 可选的3位区号+连字符
// d{8} 8位号码
// | 或者
// (?(1)d{8}) 如果前面捕获了区号,则必须匹配8位号码
bool match1 = Regex.IsMatch("12345678", pattern);      // True(无区号)
bool match2 = Regex.IsMatch("010-12345678", pattern);  // True(有区号和连字符)
bool match3 = Regex.IsMatch("01012345678", pattern);   // False(有区号但无连字符)

3. 正则表达式的惰性匹配与贪婪匹配

string text = "<tag>内容1</tag><tag>内容2</tag>";

// 贪婪匹配(默认):尽可能匹配最长的字符串
string greedyPattern = @"<tag>.*</tag>";
Match greedyMatch = Regex.Match(text, greedyPattern);
Console.WriteLine("贪婪匹配:" + greedyMatch.Value); // 输出:<tag>内容1</tag><tag>内容2</tag>

// 惰性匹配:尽可能匹配最短的字符串
string lazyPattern = @"<tag>.*?</tag>";
Match lazyMatch = Regex.Match(text, lazyPattern);
Console.WriteLine("惰性匹配:" + lazyMatch.Value);   // 输出:<tag>内容1</tag>

十一、总结

正则表达式是 C# 中处理文本的强大工具,通过System.Text.RegularExpressions命名空间提供的类,可以实现复杂的文本模式匹配、提取、替换和拆分操作。本文介绍了:

  1. 正则表达式的基础语法和核心类
  2. 匹配、替换、拆分等基本操作
  3. 分组捕获和命名分组的使用
  4. 正则表达式选项和性能优化
  5. 常见应用场景和示例

使用正则表达式时,应注意:

  • 避免过度复杂的模式,保持可读性
  • 对于频繁使用的正则表达式,使用RegexOptions.Compiled提高性能
  • 适当使用超时设置,防止恶意输入导致的性能问题
  • 复杂验证逻辑可结合代码和正则表达式实现

掌握正则表达式将极大提高文本处理效率,无论是数据验证、日志分析还是文本转换,都能发挥重要作用。在实际开发中,建议结合具体需求选择合适的正则表达式,并充分测试各种边界情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿蒙Armon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值