正则表达式是判断、匹配和分解一定模式的字符串的法宝。C#强大的基础类库提供的System.Text.RegularExpressions命名空间里包含的Regex类,可以充分发挥正则表达式的威力。

需要查看C#中正则表达式里各个符号的意义的请查阅这里,不过看一遍也很难记住,可以用的时候再查询,用的次数多了就会记住那些使用频率较高的符号了。

0. 简单介绍C#里Regex类的用法

(1)检查一个字符串是否符合指定的模式,如pattern=\"^[0-9]*$\"

Regex regex = new Regex(pattern);
if (regex.IsMatch(test)) 
  Console.WriteLine(\"{0} matches the pattern.\", test); 
else Console.WriteLine(\"{0} does not match the pattern.\", test);

(2)提取一定模式的字符串

Match match = regex.Match(text);
MatchCollection matches = regex.Matches(text);

如果能够确定text里面只有一个子串满足模式pattern,可以用Match;如果可能有多个匹配,则可以使用Matches。当然若我们只需要从包含多个pattern的text中找到第一个,那么也可以用Match
Match中两个重要的属性是ValueIndex,其中Value为满足设定模式的子串,Index为该子串在输入字符串中的开始位置。
另外,如果我们匹配的模式中有多个项,如\"\\b(?\\w+)\\s+(\\k)\\b\"表示重复的单词,它的模式串中有两个word,我们最后得到的Match.Value可能是“dog dog”,而若我们想得到单个单词的话还需要进一步对这个子串进行划分。但是Match中还有一个重要的属性已经帮我们做好了这一步,Match.Groups获取由正则表达式匹配的组的集合,对于上面的例子Match.Groups[\"word\"].Value就表示“dog”。

Regex rx = new Regex(@\"\\b(?\\w+)\\s+(\\k)\\b\", RegexOptions.Compiled | RegexOptions.IgnoreCase);
string text = \"The the quick brown fox  fox jumped over the lazy dog dog.\";

Match singleMatch = rx.Match(text);
Console.WriteLine(\"Value: {0}\", singleMatch.Value);
Console.WriteLine(\"Index: {0}\", singleMatch.Index);

MatchCollection matches = rx.Matches(text);
foreach (Match match in matches)
{
    GroupCollection groups = match.Groups;
    Console.WriteLine(\"\'{0}\' repeated at positions {1} and {2}\",
                       groups[\"word\"].Value,
                       groups[0].Index,
                       groups[1].Index);
 }

(3)替换匹配的子串
string replactedText = rx.Replace(text, myEvaluator);
这里的参数myEvaluatorMatchEvaluator委托。下面的例子中我们专门定义了方法ReplaceCC2C()来将重复出现的单词去掉一个(即替换成单个单词)。MatchEvaluator委托的输入是Match,输出是要替换Match.Value的字符串。
如果替换的方法比较简单时,我们可以省去专门定义一个方法的力气,直接使用匿名方法,如

var test = rx.Replace(text, delegate(Match m) { return m.Groups[\"word\"].Value; });
public void ExtractMatches()
{
    Regex rx = new Regex(@\"\\b(?\\w+)\\s+(\\k)\\b\", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    string text = \"The the quick brown fox  fox jumped over the lazy dog dog.\";

    RegexExample ex = new RegexExample();
    MatchEvaluator myEvaluator = new MatchEvaluator(ex.ReplaceCC2C);
    var replactedText = rx.Replace(text, myEvaluator);
    //var replactedText = rx.Replace(text, delegate(Match m) { return m.Groups[\"word\"].Value; });
    Console.WriteLine(text);
    Console.WriteLine(replactedText);
}

public string ReplaceCC2C(Match m)
{
    return m.Groups[\"word\"].Value;
}

输出结果:

The the quick brown fox  fox jumped over the lazy dog dog.
The quick brown fox jumped over the lazy dog.

(4)拆分字符串
string[] parts = rx.Split(text);
虽然C#string也有Split函数,但是它的参数只能是确定的字符数组,而RegexSplit函数则是利用正则表达式模式将输入字符串拆分成子字符串数组。

下面我们从一些常见的例子来尝试使用正则表达式,上面已经介绍的Regex的用法,下面的例子都是检查是否满足某模式串,都是用IsMatch,所以接下来我只会列出模式串。

1. 验证我国的座机电话号码

首先我们列出所有想要支持的电话号码的格式,然后再分类看怎么写正则表达式。
规则:我国的座机号码分为区号和号码,区号与号码之间用短横‘-’或者括号‘()’来分割,另外最前面还可能会加上我国的区号86或者+86,下面列举出一些合法的电话号码:
027-88888888
0722-7777777
(027)88888888
(0722)7777777
86(027)88888888
+86(0722)7777777
所以最后写出的表达式为:
\"^(\\+)?(86)?0\\d{2,3}-\\d{7,8}$|^(\\+)?(86)?\\(0\\d{2,3}\\)\\d{7,8}$\"

另外,我看到有人这样写\"0\\d{2,3}-\\d{7,8}|\\(0\\d{2,3}\\)\\d{7,8}\",虽然上面的例子都能通过,但是它可能会使得027-888888889999也是合法的,因为上述表达式是在输入字符串里找到一个子串满足我们的模式要求,只要存在这样的子串IsMatch就会返回true。所以我们需要在对模式串加上开始字符^和结束字符$来避免这种情况。

2. 验证手机电话号码

规则
手机号码为11位数字,首位一定是1,到目前为止第二位数字可以是3,4,5,7,8,其他位可以是0-9的任意数字。格式上可以是所有数字连写,也可以在中间加上短横分割。
正例
13012345678
15887654321
178-23760930
178-2376-0930
反例
1301234567
158876543212
178-2376-0930-
正则表达式\"^1[34578]+\\d{1}((-)?\\d{4}){2}$\"
逐字符解释^表示电话号码的开始,1表示第一位字符一定要是1,[34578]+表示第二位字符是3,4,5,7,8中的至少一个,\\d{1}表示一个数字,同理\\d{4}表示4个数字,(-)?表示短横可以出现0次或1次,((-)?\\d{4}){2}表示(-)?\\d{4}出现两次,$表示电话号码结束。

3. 验证电子邮箱

规则:必须以字母开始(有人说有邮箱可以以下划线开始,比如说Yahoo,但是我专门去注册试了试,人家要求一定要以字母开始);电子邮箱中包含且仅包含一个@字符,该字符将邮箱划分为两个部分;前部分为用户名,可以由字母、数字、下划线、短横或点组成;不能出现多个短横或点相连的情况;
正例
12345@qq.com
test.Name@163.com
full_name@google.com
__12-345@qq.com
反例
__12–345@qq.com
正则表达式\"^[A-Za-z]+([-.]\\w+)*@\\w+([-.]\\w+)*\\.[a-z]{2,3}$\"
逐字符解释^[A-Za-z]+表示至少有一个字母,\\w+表示至少一个包括下划线的任何单词字符,等价于[A-Za-z0-9_]+([-.]\\w+)*表示零个或者多个[-.]\\w+,而[-.]\\w+表示-或者.加上包括下划线的任何单词字符,这样写正好可以把多个短横或者点号用字符分隔开,@字符后面的前半部分与之前的类似,最后一部分为\\.[a-z]{2,3},表示后缀为.加上2个或者2个小写字母。
:有人可能会说qq邮箱前面应该全部都是数字,所以上面的表达式不松了。这就看大家的使用环境了,比如说有些邮箱对用户名的长度也有限制,所以还是不要想一劳永逸了吧。只要在使用的时候把应用场景下的所有情况列举出来,能够满足要求即可。

4. 验证身份证号码

规则:早期的身份证号码是15位的全数字;现在的身份证号码为18位,由17位数字和1位校验字符组成;最后的校验字符可能0-10,其中10由字母X或者x表示。
正例
429001197806281
429001197806281234
42900119780628123X
42900119780628123x
反例
42900119780628123a
4290011978062812
4290011978062812345
正则表达式\"^\\d{15}$|^\\d{18}$|^\\d{17}[Xx]+$\"
逐字符解释:以上分了三种情况,\\d{15}表示15位数字,\\d{17}[Xx]+表示17位数字再加上一位X或者x。
:其实现在身份证上的前面17位数字也不是任意的0-9的数字,比如说出生日期里的年月日都应该是有限制的,上述表达式并没有严格考虑这些,大家根据需要增加更严格的验证。

5. 验证IP地址

规则:点分十进制的IPV4地址,格式为X.X.X.X,其中X为0-255。
正例
127.0.0.1
255.255.255.255
0.0.0.0
248.250.198.23
反例
256.0.0.0
00.0.0.1
127.012.032.1234
正则表达式\"^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)$\"
逐字符解释:为了更好地表示IP地址中对数字的限制,这里我们将0-255划分成了多个区段250-255,240-249,100-199,10-99,0-9

小结一些常见的模式

^,匹配字符串的开始
\\d,用来匹配0到9的数字
\\w,用来匹配字母数字或者下划线
(xx)?,用来表示xx出现零次或者一次
(xx)+,用来表示xx至少出现一次
(xx)*,用来表示xx出现零次或者多次
(xx){2,3},用来表示xx出现两次或者三次
[123abc],其中[]用来表示里面出现的字符是或的关系
$,匹配字符串的结束

参考文献:

  1. Regex 类
  2. C#中正则表达式的使用
  3. C#正则表达式Regex类的用法
  4. C#正则表达式语法大全