正则表达式语言可以识别各种字符模式。.NET
中的正则表达式基于Perl 5
的正则表达式,并支持搜索和替换功能。
所有的正则表达式相关类型都定义在System.Text.RegularExpressions
命名空间中。
正则表达式基础
量词符号是正则表达式最常用的运算符之一。?
表示匹配运算发前的项目0次或者1次。例如,正则表达式"colou? r"
即可以匹配color
也可以匹配colour
,但不能匹配colouur
:
Console.WriteLine(Regex.Match("color", @"colo?r").Success); //True
Console.WriteLine(Regex.Match("colour", @"colou?r").Success); //True
Console.WriteLine(Regex.Match("colouur", @"colou?r").Success); //False
Regex.Match
方法可以在一个大型字符串内进行搜索。其返回的对象的属性既包含了匹配部分所在的Index
(位置)和Length
(长度),还包含了具体的匹配值Value
:
Match m = Regex.Match("something you have to do?", @"you?");
Console.WriteLine(m.Success); //True
Console.WriteLine(m.Index); //10
Console.WriteLine(m.Length); //3
Console.WriteLine(m.Value); //you
Console.WriteLine(m.ToString()); //you
Regex.Match
就像是string
类型的IndexOf
方法的增强版本。只不过前者搜索的是一种模式,而不是普通的字面量。
IsMatch
方法相当于先调用Match
方法,再测试其返回值的Success
属性。
默认情况下,正则表达式引擎将按照从左到右的顺序对字符串进行匹配。因此它总会返回左起第一个匹配值。如需返回更多的匹配值,请调用NextMatch
方法:
Match m1 = Regex.Match("One color? There are two colours in my head!", @"colou?rs?");
Match m2 = m1.NextMatch();
Console.WriteLine(m1); //color
Console.WriteLine(m2); //colours
Matches
方法则会返回一个包含所有匹配值的数组。因此上例可写为:
foreach (Match m in Regex.Matches("One color? There are two colours in my head!", @"colou?rs?"))
{
Console.WriteLine(m);
}
另一个常见的正则表达式运算符是替换运算符(alternator)
,用一个竖线表示:|
。替换运算符代表了一种替代关系。例如,以下语句可以匹配“Jen”、“Jenny”和“Jennifer”
:
Console.WriteLine(Regex.IsMatch("Jenny", @"Jen(ny|nifer)?")); //True
Console.WriteLine(Regex.IsMatch("Jennifer", @"Jen(ny|nifer)?")); //True
从.NET Framework 4.5开始,正则表达式支持在匹配操作中指定超时时间。如果匹配操作超出了指定的时间间隔(TimeSpan)就会抛出RegexMatch-TimeoutException。如果应用程序需要处理任意的正则表达式(例如,在高级搜索对话框中)则务必使用该参数以防止一些恶意的正则表达式导致的无限计算。
编译正则表达式
实例化Regex
对象,指定模式与RegexOptions.Compiled
选项,调用该对象的实例方法进行匹配:
Regex r = new Regex(@"sausages?", RegexOptions.Compiled);
Console.WriteLine(r.Match("sausage")); //sausage
Console.WriteLine(r.Match("sausages")); //sausages
RegexOptions.Compiled
选项将会使Regex
实例通过轻量级的代码生成器(Re-flection.Emit命名空间下的DynamicMethod)动态地构建并编译针对特定正则表达式的代码。这种方式能提高匹配速度,但是需要额外的编译开销。
RegexOptions属性
RegexOptions
枚举可以控制正则表达式匹配的行为。一个常见用法是在匹配中忽略大小写:
Console.WriteLine(Regex.Match("a", "A", RegexOptions.IgnoreCase)); // a
上述程序会在匹配中使用当前文化的大小写比较规则。如需使用不变文化,则应指定CultureInvariant
标记:
Console.WriteLine(Regex.Match("a", "A", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant));
大多数RegexOptions
标志也可以在正则表达式内使用单字母代码激活。例如:
Console.WriteLine(Regex.Match("a", @"(?i)A")); //a
在使用完毕后,也可以通过另一个表达式(-i)
来关闭该选项,例如:
Console.WriteLine(Regex.Match("AAAa", @"(?i)a(?-i)a")); //Aa
Console.WriteLine(Regex.Match("AAAa", @"(?i)a(?i)a")); //AA
另一个常用的选项是IgnorePatternWhitespace
或者(? x)
。该选项允许在正则表达式中添加空白字符增强其可读性。如果不使用该选项,则表达式中的空白字符就会作为字面量。
常用RegexOptions的值及对象选项字母
字符转义
正则表达式有以下几种元字符,这些元字符不会作为字面量来处理,它们具有特殊的含义:
· \ * + ? | { [ () ^ $ . #
如果需要使用这些元字符的字面量,则需要在之前添加反斜线字符。
比如:
Console.WriteLine(Regex.Match("what?", @"what\?")); //what?
Regex
的Escape
方法可以将包含元字符的字符串替换为转义形式,Unescape
方法则正好相反。例如:
Console.WriteLine(Regex.Escape(@"?")); //\?
Console.WriteLine(Regex.Unescape(@"\?"));// ?
字符集合
正则表达式中的字符集合是一系列的通配符.
若要匹配集合中的一个字符,则需要将这些字符放在方括号中:
Console.WriteLine(Regex.Matches("That is that.", @"[Tt]hat").Count); //2
如果要匹配集合之外的字符,则需要将方括号内第一个字符前添加^
符号:
Console.WriteLine(Regex.Match("quiz qwerty", @"q[^aeui]").Index); //5
Console.WriteLine(Regex.Match("quiz qwerty", @"q[^uw]").Success); //False
可以使用连字符定义一个字符范围。例如,以下正则表达式匹配了国际象棋棋子的移动规则:
Console.WriteLine(Regex.Match("b1-c4",@"[a-h]\d-[a-h]\d").Success);
-
\d
表示一个十进制数字,因此\d
匹配任意数字。 -
\D
匹配非数字字符。\w
匹配一个单词字符。包括字母、数字、下划线。 -
\W
匹配任何非单词字符。该规则同时适用于非英文字符,例如西里尔字母(Cyrillic
)。 -
.
匹配\n
之外的任意的字符(它可以匹配\r
)。 -
\p
匹配指定类型的字符。例如{Lu}匹配大写字母,或者{P}匹配标点符号。
Console.WriteLine(Regex.Match("Yes, please", @"\p{P}")); //,
量词符号
量词符号匹配特定次数的项目。
*
可以对出现零次或者多次的字符和组进行匹配.
Console.WriteLine(Regex.Match("cv15.doc", @"cv\d*\.doc")); //cv15.doc
Console.WriteLine(Regex.Match("cv1.doc", @"cv\d*\.doc")); //cv1.doc
Console.WriteLine(Regex.Match("cv150.doc", @"cv\d*\.doc")); //cv150.doc
量词符号+
可以对出现一次到多次的字符和组进行匹配。例如:
Console.WriteLine(Regex.Matches("helllo, chinese new year!", @"hel+\w").Count); //1
量词符号{}
则匹配特定的次数或者次数范围。例如,以下正则表达式匹配数值:
Regex bp = new Regex(@"\d{2,3}/\d{2,3}");
Console.WriteLine(bp.Match("It used to be 160/110")); //160/110
Console.WriteLine(bp.Match("Now it's only 115/75")); //115/75
贪婪量词符号与懒惰量词符号
默认情况下,量词符号都是贪婪,而不是懒惰的。贪婪量词符号会尽可能多地匹配重复项目。而懒惰的量词符号则尽可能少地进行匹配。若在量词符号后添加?
后缀,就可以将任何量词符号转换为懒惰的。
为了提取<i></i>
中的短语:
string html = "<i>By default</i> quantifiers are <i>greedy</i> creatures";
假设我们需要提取斜体部分的短语。执行如下代码:
foreach (Match match in Regex.Matches(html, @"<i>.*</i>"))
{
Console.WriteLine(match);
}
结果只有一个匹配 ,而非两处匹配:
<i>By default</i> quantifiers are <i>greedy</i>
这是因为量词符号*
将重复尽可能多次,直至匹配到最后一个</i>
。因此它直接越过了第一个</i>
,而仅仅到最后一个</i>
才会停止(以这个位置起始的表达式可以继续进行匹配)。
采用懒惰量词符号:
foreach (Match match in Regex.Matches(html, @"<i>.*?</i>"))
{
Console.WriteLine(match);
}
其结果:
<i>By default</i>
<i>greedy</i>
零宽度断言
正则表达式语言允许在匹配的前后设置约束条件。这些条件包括后向条件(lookbehind
)、前向条件(lookahead
)、锚点(anchors
)以及单词边界(word boundaries
)。由于它们并不会增加匹配字符的长度,因此这些条件称为零宽度断言(zero-widthassertions
)。