1、起因
最近发现程序中有一段控制TextBox数字输入的代码,相信大家都不会太陌生,如下:
void int_KeyPress(object sender, KeyPressEventArgs e) { const char Delete = (char)8; if (!Char.IsDigit(e.KeyChar) && e.KeyChar != Delete) { e.Handled = true; } }
乍一看,好像没有啥问题,但是却出现了一个bug,能够输入全角的数字,如:0、1、2、3等。错误的根源就是上面代码中用到的IsDigit函数,于是就有了下面的一番探究,让我们来看看IsDigit函数的真面目。
2、IsDigit函数
查阅MSDN,告知该函数是判断字符是Unicode的十进制数字的函数。在查看其源代码
public static bool IsDigit(char c) { if (!IsLatin1(c)) return CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber; return c >= '0' && c <= '9'; }
第一行的IsLatin1函数是判断字符是0~255的函数,而全角的0、1、2、3等的Unicode编码不在这个范围,于是就执行了下面这句代码:
CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber;
而对于Unicode的分类中,半角的1,2,3和全角的0、1、2、3等都被归为了DecimalDigitNumber,所以对于全角的数字,这个函数返回了true。
还有一个函数IsNumber和IsDigit功能相似,我们是否可以用它来代替呢?看下面的分析
3、IsNumber函数又是何物?
先看看这个函数的定义
public static bool IsNumber(char c) { if (!IsLatin1(c)) return CheckNumber(CharUnicodeInfo.GetUnicodeCategory(c)); if (!IsAscii(c)) return CheckNumber(GetLatin1UnicodeCategory(c)); return c >= '0' && c <= '9'; }
internal static bool CheckNumber(UnicodeCategory uc) { switch (uc) { case UnicodeCategory.DecimalDigitNumber: case UnicodeCategory.LetterNumber: case UnicodeCategory.OtherNumber: return true; } return false; }
和IsDigit函数相比有3点区别:
1)多了一个UnicodeCategory.LetterNumber类型
2)多了一个UnicodeCategory.OtherNumber类型
3)多了一个IsAscii的判断(0~127)
很显然IsNumber的范围更广了。下面列举几种IsNumber认为是数字的字符
UnicodeCategory.LetterNumber:Ⅰ、Ⅱ、Ⅲ
UnicodeCategory.OtherNumber:①、②、③
128~255中有哪些字符会被IsNumber认为是数字,有兴趣的可以自己去测试。
测试的方法可以利用这个函数:System.Globalization.CharUnicodeInfo.GetUnicodeCategory(char c) ,返回的是一个UnicodeCategory类型,你可以看看是不是IsNumber的几个类型就知道了。
4、结论
搞清楚了上面这两个函数的内部实现,那么在判断是否是ASCII数字(0~9)的时候,我们就需要注意以下几点了。
1)不能用IsDigit和IsNumber函数判断是否是ASCII数字,这两个函数都有可能把ASCII以外的某些字符当做是数字。
2)尽量用这种方式判断: c >= '0' && c <= '9'(当然也可以用正则表达式)。
3)数字判断的严格性,从严到松依次是:
c >= '0' && c <= '9' ⇒IsDigit ⇒IsNumber
4)修改上面的bug函数作为结束
void int_KeyPress(object sender, KeyPressEventArgs e) { const char Delete = (char)8; if (!(e.KeyChar >= '0' && e.KeyChar <= '9') && e.KeyChar != Delete) { e.Handled = true; } }