【源碼分析】你必須知道的string.IsNullOrEmpty && string.IsNullOrWhiteSpace

寫在前面

以前自信擼碼時踩了一次小坑,代碼以下:json

private static void AppServer_NewMessageReceived(WebSocketSession session, string value)
        {
            if (string.IsNullOrEmpty(value))
             {
                return;
             }
            value = HttpUtility.UrlDecode(value);                    
            SuperSocketTemp<string> model = JsonConvert.DeserializeObject<SuperSocketTemp<string>>(value);          
             //具體業務...
        }

就是這段代碼在測試環境拋錯,提及來全是淚啊。這段代碼的具體業務場景是Websocket即時通信接收來自客戶端的消息,消息以json字符串的形式傳輸。首先判斷是否空字符串,若是不是,爲了防止亂碼進行Url解碼,而後反序列化消息解析成須要的數據格式,最後執行具體的業務操做。c#

測試環境拋的錯是萬惡的「未將對象引用到對象的實例」,很簡單就能夠定位到問題的所在——反序列化失敗了,只要在序列化以後執行具體業務邏輯以前加上非空判斷就能夠解決掉這個問題。這也怪本身思惟還不夠嚴密,沒有養成防護性編碼的習慣。session

private static void AppServer_NewMessageReceived(WebSocketSession session, string value)
        {
            if (string.IsNullOrEmpty(value))
             {
                return;
             }
            value = HttpUtility.UrlDecode(value);                    
            SuperSocketTemp<string> model = JsonConvert.DeserializeObject<SuperSocketTemp<string>>(value);
            if(model==null)
            {
                return;
            }   
             //具體業務...
        }

經過日誌分析反序列失敗的緣由,日誌中記錄的消息是空白的,可是代碼中明明有string.IsNullOrEmpty(value)的判斷,爲啥還會出現空的狀況呢?仔細一看,原來是多個連續的空格,吐血。因而乎立馬把string.IsNullOrEmpty(value)改成string.IsNullOrWhiteSpace(value),當value是多個連續的空格時,直接返回,不會繼續往下執行。socket

private static void AppServer_NewMessageReceived(WebSocketSession session, string value)
        {
            if (string.IsNullOrWhiteSpace(value))
             {
                return;
             }
            value = HttpUtility.UrlDecode(value);                    
            SuperSocketTemp<string> model = JsonConvert.DeserializeObject<SuperSocketTemp<string>>(value);
            if(model==null)
            {
                return;
            }   
             //具體業務...
        }

咱們都知道,string.IsNullOrEmpty方法是判斷字符串是否爲:null或者string.Empty;string.IsNullOrWhiteSpace方法是判斷null或者全部空白字符,功能至關於string.IsNullOrEmpty和str.Trim().Length總和。那麼具體方法內部是怎麼實現的呢?咱們能夠經過ILSpy反編譯窺探一番。源碼分析

string.IsNullOrEmpty源碼分析

// string
/// <summary>Indicates whether the specified string is null or an <see cref="F:System.String.Empty" /> string.</summary>
/// <param name="value">The string to test. </param>
/// <returns>true if the <paramref name="value" /> parameter is null or an empty string (""); otherwise, false.</returns>
[__DynamicallyInvokable]
public static bool IsNullOrEmpty(string value)
{
    return value == null || value.Length == 0;
}

string.IsNullOrEmpty實現很簡單,無非就是判斷傳入的字符串參數,當是null或者空字符串string.Empty就返回true;不然返回false。學習

string.IsNullOrWhiteSpace源碼分析

// string
/// <summary>Indicates whether a specified string is null, empty, or consists only of white-space characters.</summary>
/// <param name="value">The string to test.</param>
/// <returns>true if the <paramref name="value" /> parameter is null or <see cref="F:System.String.Empty" />, or if <paramref name="value" /> consists exclusively of white-space characters. </returns>
[__DynamicallyInvokable]
public static bool IsNullOrWhiteSpace(string value)
{
    if (value == null)
    {
        return true;
    }
    for (int i = 0; i < value.Length; i++)
    {
        if (!char.IsWhiteSpace(value[i]))
        {
            return false;
        }
    }
    return true;
}

string.IsNullOrWhiteSpace的實現就稍微複雜一些,首先當傳入的字符串參數爲null時確定返回true;若是不是就開始遍歷字符串,取出字符執行char.IsWhiteSpace(value[i])方法,若是char.IsWhiteSpace(value[i])方法返回false,就終止遍歷,返回fasle;不然返回true。因此char.IsWhiteSpace方法應該判斷的是傳入的字符是否爲空字符,是空字符返回true,不是返回false。咱們能夠進入char.IsWhiteSpace方法看一下具體實現:測試

// char
/// <summary>Indicates whether the specified Unicode character is categorized as white space.</summary>
/// <param name="c">The Unicode character to evaluate. </param>
/// <returns>true if <paramref name="c" /> is white space; otherwise, false.</returns>
[__DynamicallyInvokable]
public static bool IsWhiteSpace(char c)
{
    if (char.IsLatin1(c))
    {
        return char.IsWhiteSpaceLatin1(c);
    }
    return CharUnicodeInfo.IsWhiteSpace(c);
}

能夠發現方法內部判斷了char.IsLatin1(c),符合的話執行char.IsWhiteSpaceLatin1(c),看不明白,繼續往下走。編碼

// char
private static bool IsLatin1(char ch)
{
    return ch <= 'ÿ';
}

'ÿ'是什麼鬼?看不懂。可是char.IsWhiteSpace方法調用了CharUnicodeInfo.IsWhiteSpace(c)方法,那應該是和Unicode有關。並且到了char字符的級別上,更加能夠確定和Unicode編碼有關。從Unicode字符列表搜索'ÿ',果真搜到了。lua

mark

咱們能夠發現'ÿ'是拉丁字母輔助的最後一個字符,再日後的的字符基本不會出現,因此在大多數狀況下ch <= 'ÿ'能夠知足的。當知足ch <= 'ÿ'時執行下面的方法:

// char
private static bool IsWhiteSpaceLatin1(char c)
{
    return c == ' ' || (c >= '\t' && c <= '\r') || c == '\u00a0' || c == '\u0085';
}

IsWhiteSpaceLatin1(char c)負責判斷字符c是不是空白字符。

c == ' ' 很好理解,判斷c是否是空格字符。對應下圖:

mark

c >= '\t' && c <= '\r' 對照Unicode字符列表就能夠理解。下圖紅框圈出的5個字符都認定爲空白字符。

mark

mark

c == '\u00a0' 以下圖被認定爲空白字符。

mark

c == '\u0085' 以下圖被認定爲空白字符。

mark

知足①②③④其中任意一個,便會被斷定爲空白字符。

那麼假設char.IsLatin1(c)返回false呢?此時執行CharUnicodeInfo.IsWhiteSpace(c)。

// System.Globalization.CharUnicodeInfo
internal static bool IsWhiteSpace(char c)
{
    switch (CharUnicodeInfo.GetUnicodeCategory(c))
    {
    case UnicodeCategory.SpaceSeparator:
    case UnicodeCategory.LineSeparator:
    case UnicodeCategory.ParagraphSeparator:
        return true;
    default:
        return false;
    }
}

CharUnicodeInfo.GetUnicodeCategory(c)會返回一個UnicodeCategory枚舉類型。

// System.Globalization.CharUnicodeInfo
/// <summary>Gets the Unicode category of the specified character.</summary>
/// <param name="ch">The Unicode character for which to get the Unicode category. </param>
/// <returns>A <see cref="T:System.Globalization.UnicodeCategory" /> value indicating the category of the specified character.</returns>
[__DynamicallyInvokable]
public static UnicodeCategory GetUnicodeCategory(char ch)
{
    return CharUnicodeInfo.InternalGetUnicodeCategory((int)ch);
}

CharUnicodeInfo是一個靜態類,根據MSDN說明,Unicode標準定義了許多Unicode字符類別。例如,一個字符可能被分類爲大寫字母,小寫字母,小數位數字,字母數字,段落分隔符,數學符號或貨幣符號。所述UnicodeCategory枚舉定義了可能的字符的類別。

使用CharUnicodeInfo類來獲取特定字符的UnicodeCategory值。該CharUnicodeInfo類定義了返回下面的Unicode字符值的方法:

  • 字符或代理對所屬的特定類別。返回的值是UnicodeCategory枚舉的成員。
  • 數字值。僅適用於數字字符,包括分數,下標,上標,羅馬數字,貨幣分子,圈出的數字和腳本特定的數字。
  • 數字值。適用於可與其餘數字字符組合的數字字符,以表示編號系統中的整數。
  • 十進制數字值。僅適用於表示小數點(基10)系統中的十進制數字的字符。十進制數字能夠是十個數字之一,從零到九。這些字符是UnicodeCategory的成員DecimalDigitNumber類別。

當GetUnicodeCategory方法返回的枚舉值是UnicodeCategory.SpaceSeparatorUnicodeCategory.LineSeparatorUnicodeCategory.ParagraphSeparator其中任意之一,則斷定爲空白字符,返回true。

總結

踩坑沒關係,要緊的是要知道爲何會有這個坑。

軟件80%的bug都拜「未將對象引用到對象的實例」所賜,要養成防護性編碼的好習慣。


本文爲博主學習感悟總結,水平有限,若是不當,歡迎指正。

若是您認爲還不錯,不妨點擊一下下方的推薦按鈕,謝謝支持。

轉載與引用請註明出處。

相關文章
相關標籤/搜索