写在前面

之前自信撸码时踩了一次小坑,代码如下:

  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解码,然后反序列化消息解析成需要的数据格式,最后执行具体的业务操作。

测试环境抛的错是万恶的“未将对象引用到对象的实例”,很简单就可以定位到问题的所在——反序列化失败了,只要在序列化之后执行具体业务逻辑之前加上非空判断就可以解决掉这个问题。这也怪自己思维还不够严密,没有养成防御性编码的习惯。

  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是多个连续的空格时,直接返回,不会继续往下执行。

  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字符列表搜索'ÿ',果然搜到了。

我们可以发现'ÿ'是拉丁字母辅助的最后一个字符,再往后的的字符基本不会出现,所以在大多数情况下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是不是空格字符。对应下图:

c >= '\t' && c <= '\r' 对照Unicode字符列表就可以理解。下图红框圈出的5个字符都认定为空白字符。

c == '\u00a0' 如下图被认定为空白字符。

c == '\u0085' 如下图被认定为空白字符。

满足①②③④其中任意一个,便会被判定为空白字符。

那么假设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都拜“未将对象引用到对象的实例”所赐,要养成防御性编码的好习惯。


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

如果您认为还不错,不妨点击一下下方的【推荐】按钮,谢谢支持。

转载与引用请注明出处。

【源码分析】你必须知道的string.IsNullOrEmpty && string.IsNullOrWhiteSpace的更多相关文章

  1. 面试必会之HashMap源码分析

    相关文章 面试必会之ArrayList源码分析 面试必会之LinkedList源码分析 简介 HashMap最早出现在JDK1.2中,底层基于散列算法实现.HashMap 允许 null 键和 nul ...

  2. 深入理解.net - 4.你必须知道的String

    为什么要单独写string,主要是它太常用了,同时又太特殊了,特殊到CLR对它的处理都和其它对象不一样.简直可以称为VIP用户啊.本文并不是一篇介绍如何使用string的文章,而是旨在阐述string ...

  3. 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

    目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...

  4. Mybatis 源码分析--crud

    增加源码分析-insert() --------------------------------------------------------------------- public int ins ...

  5. Spark源码分析 – BlockManager

    参考, Spark源码分析之-Storage模块 对于storage, 为何Spark需要storage模块?为了cache RDD Spark的特点就是可以将RDD cache在memory或dis ...

  6. [源码]String StringBuffer StringBudlider(2)StringBuffer StringBuilder源码分析

      纵骑横飞 章仕烜   昨天比较忙 今天把StringBuffer StringBulider的源码分析 献上   在讲 StringBuffer StringBuilder 之前 ,我们先看一下 ...

  7. String、StringBuffer、StringBuilder源码分析

    利用反编译具体看看"+"的过程 1 public class Test 2 { 3 public static void main(String[] args) 4 { 5 int ...

  8. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

  9. Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现

    视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...

随机推荐

  1. HI3531的nand flash测试

    void NAND_Init() {    *(unsigned int *)(0x20030000 + 0xd0) = 7; delay_x(0X5000);    *(unsigned int * ...

  2. Emacs编辑器配置

    以前总是用的vim编辑器,今天突然想换emacs 用下.折腾了很久终于搞定.使用的是windows测试环境 emacs下载地址http://ftp.gnu.org/gnu/emacs/windows/ ...

  3. Rweibo , wordcloud

    利用Rweibo ,wordcloud做词云 #导入需要的包,不存在则下载 require(Rweibo) #必须先调用rJava不然Rwordseg 无法使用 library(rJava) requ ...

  4. java.lang.IllegalStateException: Failed to load ApplicationContext

    1.错误描述 七月 13, 2014 6:34:41 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBean ...

  5. DOS批处理命令递归删除给定的文件(夹),兼VC工程清理小工具

    使用dos批处理命令递归删除指定的文件(夹): (下面内容针对清理VC工程!自己按说明任意修改) 2014-06-10修改:删除前增加了[y,n]询问: echo off rem 递归删除当前文件下指 ...

  6. Django学习-5-模板渲染

    1. {{ 变量名 }}                          def func(request):                     return render(request, ...

  7. CodeM资格赛 Round A 最长树链

    按照题解的做法,对于每一个质约数分别进行讨论最长链就行 对于每一个数的质约数可是比logn还要小的 比赛的时候没人写,我也没看 = =,可惜了,不过我当时对于复杂度的把握也不大啊 #include & ...

  8. 求数组中最小的k个数

    题目:输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. package test; import java.util.Arra ...

  9. BIOS相关

    BIOS设置中恢复默认设置的选项是Load Optimized Defaults, 但是有的电脑是restore,我的就是 有的电脑进入bios需要按住Fn+F2一些操作也需要按Fn,比如说保存并退出 ...

  10. 聊聊Linux用户态驱动设计

    序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都有各自的缺点.内核态驱动的问题是: ...