写在前面

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

  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. 航空客户价值分析特色LRFMC模型——RFM升级

    本文转载自微信公众号TIpDM. 每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 我们说RFM ...

  2. FusionCharts 3D帕累托图

    1.设计3D帕累托图的页面 Pareto3D.html: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN&q ...

  3. CSS3之box-shadow

    1.属性简介 box-shadow:颜色值|inset|none|!important 2.浏览器兼容性 (1)IE不兼容,IE9和IE10未知: (2)火狐3.5(包含3.5)以上兼容 (3)Chr ...

  4. Java之List排序

    1.Java封装类 Student.java: /** * @Title:Student.java * @Package:com.you.data * @Description: * @Author: ...

  5. 芝麻HTTP: Learning to Rank概述

    Learning to Rank,即排序学习,简称为 L2R,它是构建排序模型的机器学习方法,在信息检索.自然语言处理.数据挖掘等场景中具有重要的作用.其达到的效果是:给定一组文档,对任意查询请求给出 ...

  6. CF374 Journey

    技不如人甘拜下风 这题网上说法有 建反向边和先拓扑 都是为了每个点之前将其前驱都遍历到 #include<bits/stdc++.h> using namespace std; typed ...

  7. httpclient的主要业务代码逻辑(图解)

    一,主要代码逻辑(图解) 二,两个案例的对比(图解) 三,详细案例 3.1,博文一 httppost的用法(NameValuePair(简单名称值对节点类型)核心对象) 3.2,博文二 httpcli ...

  8. 用VSCode开发一个asp.net core2.0+angular5项目(5): Angular5+asp.net core 2.0 web api文件上传

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三 ...

  9. canvas实现水波纹效果

    本文将会从水波的基本原理开始,详细讲解在canvas中模拟水波扩散,分析并计算水波的能量分布,并通过振幅模拟水波对图像的折射效果,最后实现水波特效. 水波基本原理 首先复习一波高中物理知识. 波是指振 ...

  10. 【BZOJ1216】操作系统(堆,模拟)

    [BZOJ1216]操作系统(堆,模拟) 题面 题目描述 写一个程序来模拟操作系统的进程调度.假设该系统只有一个CPU,每一个进程的到达时间,执行时间和运行优先级都是已知的.其中运行优先级用自然数表示 ...