C#正则表达式从入门到实战应用指南
1. 正则表达式基础概念解析
正则表达式(Regular Expression)是一种用于匹配和处理文本的强大工具,它通过特定的语法规则来描述字符串模式。在C#中,System.Text.RegularExpressions命名空间提供了完整的正则表达式支持。
1.1 正则表达式核心元素
正则表达式由普通字符和元字符组成:
- 普通字符:字母、数字、汉字等直接匹配自身
-
元字符:具有特殊含义的字符,如
.*+?[](){}|\等
常用元字符功能说明:
-
.匹配除换行符外的任意单个字符 -
*匹配前面的子表达式零次或多次 -
+匹配前面的子表达式一次或多次 -
?匹配前面的子表达式零次或一次 -
[]定义字符集合 -
()定义分组和捕获 -
{}定义匹配次数 -
|表示"或"关系 -
\转义字符
1.2 C#中的正则表达式类
C#提供了几个核心类来处理正则表达式:
- Regex:表示不可变的正则表达式
- Match:表示单个正则表达式匹配的结果
- MatchCollection:表示通过正则表达式匹配找到的所有匹配项的集合
- Group:表示单个捕获组的结果
- Capture:表示单个子表达式捕获的结果
2. C#正则表达式基础语法
2.1 创建正则表达式
在C#中有两种方式创建正则表达式:
- 通过构造函数创建:
Regex regex = new Regex(@"\d+"); // 匹配一个或多个数字
- 通过静态方法使用:
bool isMatch = Regex.IsMatch(input, pattern);
提示:在C#中使用正则表达式时,建议使用逐字字符串字面量(@前缀),这样可以避免对反斜杠进行转义。
2.2 常用匹配模式
- 字符匹配:
-
\d匹配数字,等价于[0-9] -
\D匹配非数字 -
\w匹配单词字符(字母、数字、下划线) -
\W匹配非单词字符 -
\s匹配空白字符(空格、制表符等) -
\S匹配非空白字符
- 数量限定:
-
{n}匹配n次 -
{n,}匹配至少n次 -
{n,m}匹配n到m次
- 位置匹配:
-
^匹配字符串开始 -
$匹配字符串结束 -
\b匹配单词边界
3. C#正则表达式实战应用
3.1 验证输入格式
验证电子邮件地址格式:
string emailPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
bool isValidEmail = Regex.IsMatch(email, emailPattern);
验证手机号码(中国大陆):
string phonePattern = @"^1[3-9]\d{9}$";
bool isValidPhone = Regex.IsMatch(phoneNumber, phonePattern);
3.2 提取字符串信息
从文本中提取所有URL:
string text = "访问我们的网站 https://example.com 或 http://test.org";
string urlPattern = @"https?://[^\s]+";
MatchCollection matches = Regex.Matches(text, urlPattern);
foreach (Match match in matches)
{
Console.WriteLine(match.Value);
}
从日志中提取日期和时间:
string logEntry = "2023-05-15 14:30:22 [INFO] System started";
string dateTimePattern = @"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}";
Match match = Regex.Match(logEntry, dateTimePattern);
if (match.Success)
{
Console.WriteLine($"Found datetime: {match.Value}");
}
3.3 替换字符串内容
隐藏敏感信息(如身份证号):
string idCard = "身份证号:510102199003077654";
string masked = Regex.Replace(idCard, @"(\d{6})\d{8}", "$1********");
Console.WriteLine(masked); // 输出:身份证号:510102********
格式化电话号码:
string phone = "13800138000";
string formatted = Regex.Replace(phone, @"(\d{3})(\d{4})(\d{4})", "$1-$2-$3");
Console.WriteLine(formatted); // 输出:138-0013-8000
4. 高级正则表达式技巧
4.1 使用分组和反向引用
分组允许我们将部分正则表达式括起来,然后可以在后面引用这些分组:
// 匹配重复的单词
string text = "This is is a test test sentence.";
string pattern = @"\b(\w+)\s+\1\b";
MatchCollection matches = Regex.Matches(text, pattern);
foreach (Match match in matches)
{
Console.WriteLine($"Found duplicate: {match.Groups[1].Value}");
}
4.2 非捕获组和原子组
-
非捕获组
(?:...):
// 匹配日期但不捕获分隔符
string datePattern = @"(\d{4})(?:-|/)(\d{2})(?:-|/)(\d{2})";
Match match = Regex.Match("2023/05/15", datePattern);
if (match.Success)
{
Console.WriteLine($"Year: {match.Groups[1].Value}, Month: {match.Groups[2].Value}, Day: {match.Groups[3].Value}");
}
-
原子组
(?>...):
// 提高匹配效率
string pattern = @"(?>a|ab)c";
// 匹配"ac"但不匹配"abc"
4.3 零宽断言
-
正向先行断言
(?=...):
// 匹配后面跟着"元"的数字
string text = "10元 20美元 30元";
string pattern = @"\d+(?=元)";
MatchCollection matches = Regex.Matches(text, pattern); // 匹配10和30
-
负向先行断言
(?!...):
// 匹配后面不跟着"美元"的数字
string pattern = @"\d+(?!美元)";
-
正向后行断言
(?<=...):
// 匹配前面有"价格:"的数字
string pattern = @"(?<=价格:)\d+";
-
负向后行断言
(?<!...):
// 匹配前面没有"折扣"的数字
string pattern = @"(?<!折扣)\d+";
5. 性能优化与最佳实践
5.1 编译正则表达式
对于频繁使用的正则表达式,可以编译以提高性能:
Regex compiledRegex = new Regex(pattern, RegexOptions.Compiled);
注意:编译正则表达式会增加启动时间,但会提高后续匹配速度。适合长期运行的应用中频繁使用的正则表达式。
5.2 使用RegexOptions优化
常用的RegexOptions选项:
-
IgnoreCase:忽略大小写 -
Multiline:多行模式,改变^和$的含义 -
Singleline:单行模式,使.匹配所有字符包括换行符 -
IgnorePatternWhitespace:忽略模式中的空白 -
CultureInvariant:忽略区域性差异
Regex regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
5.3 避免常见性能陷阱
- 避免过度回溯:
// 不好的写法:可能导致大量回溯
string badPattern = @"(a+)+b";
// 改进写法:
string goodPattern = @"a+b";
- 尽量使用具体字符集:
// 不好的写法:
string badPattern = @"\w*@\w*\.\w*";
// 改进写法:
string goodPattern = @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}";
- 使用非贪婪匹配(惰性匹配):
// 贪婪匹配(默认)
string greedy = @"<.*>"; // 会匹配到最后一个>
// 非贪婪匹配
string lazy = @"<.*?>"; // 匹配到第一个>
6. 常见问题与解决方案
6.1 匹配失败排查
- 检查特殊字符是否转义:
// 错误:试图匹配点号但没有转义
string wrongPattern = @"example.com";
// 正确:
string correctPattern = @"example\.com";
- 检查Unicode字符:
// 如果需要匹配中文字符
string chinesePattern = @"[\u4e00-\u9fa5]+";
- 检查多行模式:
// 如果不使用Multiline选项,^和$只匹配整个字符串的开头和结尾
Regex regex = new Regex(@"^abc$", RegexOptions.Multiline);
6.2 分组捕获问题
- 获取分组内容:
Match match = Regex.Match("2023-05-15", @"(\d{4})-(\d{2})-(\d{2})");
if (match.Success)
{
Console.WriteLine($"Year: {match.Groups[1].Value}"); // Groups[0]是整个匹配
Console.WriteLine($"Month: {match.Groups[2].Value}");
Console.WriteLine($"Day: {match.Groups[3].Value}");
}
- 命名分组:
Match match = Regex.Match("2023-05-15", @"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})");
if (match.Success)
{
Console.WriteLine($"Year: {match.Groups["year"].Value}");
Console.WriteLine($"Month: {match.Groups["month"].Value}");
Console.WriteLine($"Day: {match.Groups["day"].Value}");
}
6.3 替换字符串技巧
- 使用匹配结果中的分组:
string text = "John Smith, Jane Doe";
string result = Regex.Replace(text, @"(\w+)\s(\w+)", "$2, $1");
// 结果:"Smith, John, Doe, Jane"
- 使用MatchEvaluator进行复杂替换:
string text = "1kg, 2lbs, 3kg";
string result = Regex.Replace(text, @"(\d+)kg", match =>
{
double kg = double.Parse(match.Groups[1].Value);
double lbs = kg * 2.20462;
return $"{lbs:F2}lbs";
});
// 结果:"2.20lbs, 2lbs, 6.61lbs"
7. 实际项目中的应用案例
7.1 日志分析系统
从服务器日志中提取关键信息:
string logEntry = "192.168.1.1 - - [15/May/2023:14:30:22 +0800] \"GET /api/users HTTP/1.1\" 200 1234";
string pattern = @"^(?<ip>\S+) \S+ \S+ \[(?<datetime>[^\]]+)\] ""(?<method>\S+) (?<url>\S+) (?<protocol>\S+)"" (?<status>\d+) (?<size>\d+)";
Match match = Regex.Match(logEntry, pattern);
if (match.Success)
{
var logData = new
{
IP = match.Groups["ip"].Value,
DateTime = DateTime.ParseExact(match.Groups["datetime"].Value, "dd/MMM/yyyy:HH:mm:ss zzz", CultureInfo.InvariantCulture),
Method = match.Groups["method"].Value,
Url = match.Groups["url"].Value,
Status = int.Parse(match.Groups["status"].Value),
Size = int.Parse(match.Groups["size"].Value)
};
Console.WriteLine($"IP: {logData.IP}, Method: {logData.Method}, Status: {logData.Status}");
}
7.2 数据清洗工具
清洗CSV文件中的不规范数据:
string dirtyCsv = "1,\"John \"\"The Boss\"\" Smith\",100.50\n2,Mary Johnson,\"1,250.75\"";
string pattern = @"
(?<=^|,) # 前面是行首或逗号
(?: # 非捕获组
""([^""]*(?:""""[^""]*)*)"" # 匹配引号内的内容,处理内部转义引号
| # 或
([^,""\r\n]*) # 匹配非特殊字符
)
(?=,|$) # 后面是逗号或行尾
";
var lines = dirtyCsv.Split('\n');
foreach (string line in lines)
{
MatchCollection matches = Regex.Matches(line, pattern,
RegexOptions.IgnorePatternWhitespace);
foreach (Match match in matches)
{
string value = !string.IsNullOrEmpty(match.Groups[1].Value)
? match.Groups[1].Value.Replace("\"\"", "\"")
: match.Groups[2].Value;
Console.Write($"{value}\t");
}
Console.WriteLine();
}
7.3 模板引擎实现
简单的模板变量替换:
string template = "Hello, {{name}}! Today is {{date}}. Your balance is {{balance}}.";
Dictionary<string, object> data = new Dictionary<string, object>
{
{"name", "John"},
{"date", DateTime.Now.ToString("yyyy-MM-dd")},
{"balance", 1250.50m}
};
string result = Regex.Replace(template, @"\{\{(?<key>\w+)\}\}", match =>
{
string key = match.Groups["key"].Value;
return data.ContainsKey(key) ? data[key].ToString() : match.Value;
});
Console.WriteLine(result);
8. 测试与调试正则表达式
8.1 单元测试中的正则表达式
使用单元测试验证正则表达式:
[TestMethod]
public void TestEmailRegex()
{
string pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
Assert.IsTrue(Regex.IsMatch("test@example.com", pattern));
Assert.IsTrue(Regex.IsMatch("user.name+tag@sub.domain.co.uk", pattern));
Assert.IsFalse(Regex.IsMatch("invalid@email", pattern));
Assert.IsFalse(Regex.IsMatch("no@tld.", pattern));
}
8.2 使用在线工具调试
推荐几个有用的在线正则表达式测试工具:
- Regex101 (支持.NET正则语法)
- Regexr
- Debuggex
这些工具可以:
- 高亮显示匹配部分
- 解释正则表达式结构
- 显示匹配过程
- 测试不同输入字符串
8.3 性能测试与优化
测试正则表达式性能:
Stopwatch sw = new Stopwatch();
string input = new string('a', 1000000) + "b";
string pattern = @"(a+)+b"; // 有性能问题的模式
sw.Start();
bool isMatch = Regex.IsMatch(input, pattern);
sw.Stop();
Console.WriteLine($"匹配结果: {isMatch}, 耗时: {sw.ElapsedMilliseconds}ms");
优化建议:
- 对于复杂正则表达式,考虑拆分为多个简单正则表达式
- 使用非贪婪匹配减少回溯
- 避免嵌套的量词
- 使用具体字符类代替通用字符类
- 考虑使用字符串操作替代简单正则表达式
9. 正则表达式资源推荐
9.1 学习资源
- 书籍:
- 《精通正则表达式》(Jeffrey Friedl)
- 《正则表达式必知必会》(Ben Forta)
- 在线教程:
- Microsoft官方文档:.NET正则表达式
- 正则表达式30分钟入门教程
- 参考手册:
- .NET正则表达式语法速查表
- 正则表达式元字符参考
9.2 实用工具
- Visual Studio插件:
- Regex Tester
- Regex Editor
- 独立工具:
- RegexBuddy
- Expresso
- 在线测试:
- Regex101
- Regexr
9.3 进阶主题
- 正则表达式引擎原理
- DFA与NFA实现差异
- 正则表达式优化算法
- 正则表达式与编译器理论
- 正则表达式在词法分析中的应用
10. 个人经验分享
在实际项目中使用正则表达式多年,总结出以下几点经验:
-
不要过度使用正则表达式。对于简单的字符串操作,String类的方法(如Contains、StartsWith、Split等)通常更高效。
-
复杂的正则表达式一定要写注释。可以使用
RegexOptions.IgnorePatternWhitespace选项,然后在正则表达式中添加注释:
string pattern = @"
^ # 字符串开始
(?<user>[^@]+) # 捕获@前的所有字符作为用户名
@ # @符号
(?<domain>[^@]+) # 捕获@后的所有字符作为域名
$ # 字符串结束
";
Regex regex = new Regex(pattern, RegexOptions.IgnorePatternWhitespace);
- 对于用户输入的正则表达式,一定要进行验证和超时设置,避免ReDoS攻击:
try
{
Regex regex = new Regex(userPattern, RegexOptions.None, TimeSpan.FromSeconds(1));
// 使用regex...
}
catch (RegexMatchTimeoutException)
{
Console.WriteLine("正则表达式匹配超时");
}
-
在团队项目中,为复杂的正则表达式编写单元测试,确保其行为符合预期且不会因为修改而破坏现有功能。
-
性能关键路径上的正则表达式要考虑预编译(RegexOptions.Compiled),但要注意这会增加启动时间和内存使用。
-
当正则表达式变得过于复杂时(比如超过一屏幕),考虑使用解析器组合器或其他文本处理技术替代。
更多推荐


所有评论(0)