【源码分析】你必须知道的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都拜“未将对象引用到对象的实例”所赐,要养成防护性编码的好习惯。


本文为博主学习感悟总结,水平有限,若是不当,欢迎指正。

若是您认为还不错,不妨点击一下下方的推荐按钮,谢谢支持。

转载与引用请注明出处。

相关文章
相关标签/搜索