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; } }