尝试过写 if (x is null)?它与 if (x == null) 相比,孰优孰劣呢?

x is null 还有 x is constant 是 C# 7.0 中引入的模式匹配(Pattern Matching)中的一个小细节。阅读本文将了解 x is constantx == constant 之间的差别,并给出一些代码编写建议。


 

C# 7 的模式匹配

说到 C# 中新增的模式匹配,想必大家一定不会忘了变量的匹配。以下例子来自于微软官方 C# 7.0 的介绍文档 What’s New in C# 7 - C# Guide - Microsoft Docs

public static int DiceSum2(IEnumerable<object> values)
{
var sum = 0;
foreach(var item in values)
{
if (item is int val)
sum += val;
else if (item is IEnumerable<object> subList)
sum += DiceSum2(subList);
}
return sum;
}
public static int DiceSum3(IEnumerable<object> values)
{
var sum = 0;
foreach (var item in values)
{
switch (item)
{
case int val:
sum += val;
break;
case IEnumerable<object> subList:
sum += DiceSum3(subList);
break;
}
}
return sum;
}

其实,官方文档中也顺带提及了常量的匹配:

public static int DiceSum5(IEnumerable<object> values)
{
var sum = 0;
foreach (var item in values)
{
switch (item)
{
case 0:
break;
case int val:
sum += val;
break;
case PercentileDie die:
sum += die.Multiplier * die.Value;
break;
case IEnumerable<object> subList when subList.Any():
sum += DiceSum5(subList);
break;
case IEnumerable<object> subList:
break;
case null:
break;
default:
throw new InvalidOperationException("unknown item type");
}
}
return sum;
}

然而,微软居然只在 switch-case 里面说了常量的匹配,而且 case 0case null 这不本来就是我们以前熟悉的写法吗!(只不过以前只能判断一个类型的常量)


x is null Vs. x == null

好了,回到正题。我们想说的是 x is nullx == null。为了得知它们的区别,我们写一段代码:

private void TestInWalterlvsDemo(object value)
{
if (value is null)
{
}
if (value == null)
{
}
}

反编译看看:

.method private hidebysig instance void
TestInWalterlvsDemo(
object 'value'
) cil managed
{
.maxstack 2
.locals init (
[0] bool V_0,
[1] bool V_1
) // [37 9 - 37 10]
IL_0000: nop // [38 13 - 38 31]
IL_0001: ldarg.1 // 'value'
IL_0002: ldnull
IL_0003: ceq
IL_0005: stloc.0 // V_0 IL_0006: ldloc.0 // V_0
IL_0007: brfalse.s IL_000b // [39 13 - 39 14]
IL_0009: nop // [40 13 - 40 14]
IL_000a: nop // [41 13 - 41 31]
IL_000b: ldarg.1 // 'value'
IL_000c: ldnull
IL_000d: ceq
IL_000f: stloc.1 // V_1 IL_0010: ldloc.1 // V_1
IL_0011: brfalse.s IL_0015 // [42 13 - 42 14]
IL_0013: nop // [43 13 - 43 14]
IL_0014: nop // [44 9 - 44 10]
IL_0015: ret } // end of method MainPage::Test

x is null 对应的是:

IL_0001: ldarg.1      // 'value'
IL_0002: ldnull
IL_0003: ceq
IL_0005: stloc.0 // V_0

ldarg.1 将第 1 号参数压到评估栈(为什么不是第 0 号?因为第 0 号是 this)。然后将 ldnullnull 压到评估栈上。随后,ceq 比较压入的两个值是否相等。(注意是比较栈中的值哦,不会看引用的对象的!所以如果是引用类型,则比较的是引用本身哦,类似于指针!) 此处划重点,因为考试要考!咳咳……哦不,是后面要用到……

x == null 对应的是:

IL_000b: ldarg.1      // 'value'
IL_000c: ldnull
IL_000d: ceq
IL_000f: stloc.1 // V_1

于是发现两个完全一样!!!-_- 本文完,全剧终。


x is 常量 Vs. x == 常量

如果只是像上面那样,那这篇文章也太没营养了!现在我们把 null 换成其它常量:

private void TestInWalterlvsDemo(object value)
{
if (value is 1)
{
}
if (value == 1)
{
}
}

呀……编译不通过!改改……

private void TestInWalterlvsDemo(object value)
{
if (value is 1)
{
}
if (value == (object) 1)
{
}
}

于是再看看反编译出来的结果。

value is 1

IL_0001: ldc.i4.1
IL_0002: box [mscorlib]System.Int32
IL_0007: ldarg.1 // 'value'
IL_0008: call bool [mscorlib]System.Object::Equals(object, object)
IL_000d: stloc.0 // V_0

value == (object) 1

IL_0013: ldarg.1      // 'value'
IL_0014: ldc.i4.1
IL_0015: box [mscorlib]System.Int32
IL_001a: ceq
IL_001c: stloc.1 // V_1

现在已经不一样了,前者再比较时用的是 call,调用了 bool [mscorlib]System.Object::Equals(object, object) 方法;而后者依然用的是 ceq

区别已经很明显了,前者会根据具体类型具体判断相等,也就是说引用类型会调用引用类型自己的方法判断相等,值类型也会调用值类型的方法判断相等。而后者依然是比较评估栈中的两个值是否相等。关键是这两者均出现了装箱!也就是说——因为装箱的存在,对后者而言,ceq 会压入 0,即永远返回 false,这就是 BUG 所在。这就是不一样的地方!


回顾模式匹配中的常量匹配

在 C# 7 的模式匹配中,null 和常量其实都一样是常量,本来都是会调用 Object.Equals(object, object) 静态方法进行比较的;但 null 因为其特殊性,被编译器优化掉了,于是 x is nullx == null 完全一样;x is constantx == constant 依然有区别。

从反编译的 MSIL 代码中我们也可以得出一些代码编写上的建议。在比较常量的时候,如果可能,尽量使用 is 进行比较,而不是 ==。好处多多:

  • 如果是 null,写 x is null 很符合英语的阅读习惯,代码阅读起来比较舒适。
  • 如果是值常量,可以避免装箱带来的相等判断错误问题

参考资料

从 “x is null 和 x == null” 的区别看 C# 7 模式匹配中常量和 null 的匹配的更多相关文章

  1. js中NAN、NULL、undefined的区别

    NaN:保留字(表明数据类型不是数字) undefined:对象属性或方法不存在,或声明了变量但从未赋值.即当你使用了对象未定的属性或者未定义的方法时或当你声明一个变量,但你确从未对其进行赋值,便对其 ...

  2. JS中如何判断null、undefined与NaN

    1.判断undefined: <span style="font-size: small;">var tmp = undefined; if (typeof(tmp)  ...

  3. NULL, '\0',0 '0'的区别

    如题,在程序中经常遇到NULL,和'\0',常常疑惑它们是什么关系,其实它们的值是一样的,只不过表现的形式不一样: 1.NULL; NULL 即空指针,在C和C++中的形式不一样,msdn上有如下的内 ...

  4. 区分JS中的undefined,null,"",0和false

    在程序语言中定义的各种各样的数据类型中,我们都会为其定义一个"空值"或"假值",比如对象类型的空值null,.NET Framework中数据库 字段的空值DB ...

  5. javascript中的undefined,null,"",0和false的云集

    在各种各样的数据类型中,我们都会为其定义一个"空值"或"假值",比如对象类型的空值null,.NET Framework中数据库字段的空值DBNull,bool ...

  6. iOS中使用nil NULL NSNULL的区别

    nil NULL NSNULL的区别主要以下几点 1.nil:一般赋值给空对象 2.NLL:一般赋值给nil之外的其他空值.入SEL等. 3.NSULL:NSNULL只有一种方法+ (NSNull * ...

  7. json中头疼的null

    在服务器返回 json 数据的时候,时常会出现如下数据 "somevalue":null 这个时候,json 解析的时候,就会吧这个 null 解析成 NSNull 的对象,我们向 ...

  8. delphi中nil、null、UnAssigned区别

    nil:空指针,空地址,对象也是指针,所以可以object := nil;null:null是一个未定义值的变量,既不是0也不代表空字符串,它是未定义的.http://www.delphibasics ...

  9. win10 uwp Window.Current.Dispatcher中Current为null

    本文说的是进行网络中异步界面出现的错误,可能带有一定的主观性和局限性,说的东西可能不对或者不符合每个人的预期.如果觉得我有讲的不对的,就多多包含,或者直接关掉这篇文章,但是请勿生气或者发怒吐槽,可以在 ...

随机推荐

  1. 通俗易懂讲解IO模型

    前言 说到IO模型,都会牵扯到同步.异步.阻塞.非阻塞这几个词.从词的表面上看,很多人都觉得很容易理解.但是细细一想,却总会发现有点摸不着头脑.自己也曾被这几个词弄的迷迷糊糊的,每次看相关资料弄明白了 ...

  2. 机器学习笔记—混合高斯和 EM 算法

    本文介绍密度估计的 EM(Expectation-Maximization,期望最大). 假设有 {x(1),...,x(m)},因为是无监督学习算法,所以没有 y(i). 我们通过指定联合分布 p( ...

  3. 数组中的k个最小值

    问题:输入n个整数,找出其中最小的k个数. 方案一:将输入的n个整数进行排序,输出前k个数即为所求的k个最小数.时间复杂度为O(nlogn). 方案二:创建一个大小为k的容器,来存储最小的k个数.遍历 ...

  4. 1004: [HNOI2008]Cards burnside定理

    https://www.lydsy.com/JudgeOnline/problem.php?id=1004 输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替,且对每种洗牌法,都存在一种洗牌法使 ...

  5. 聊一聊Spring AOP

    前两天,在给新入职的同事做技术介绍时,讲到spring的AOP.使我又一次认识到,对于AOP,特别是spring AOP的理解,虽然大家都能说上来几句,但是许多人认识并不太全面,甚至可以说是一知半解- ...

  6. clipboard.js使用方法

    HTML data-clipboard-action=“ copy ”  或者“cut” data-clipboard-target="#domName" data-clipboa ...

  7. UVA-11374 Airport Express (dijkstra+枚举)

    题目大意:n个点,m条无向边,边权值为正,有k条特殊无向边,起止点和权值已知,求从起点到终点的边权值最小的路径,特殊边最多只能走一条. 题目分析:用两次dijkstra求出起点到任何一个点的最小权值, ...

  8. windows使用pip安装selenium报错问题

    UnicodeDecodeError: 'ascii' codec can't decode byte 0xb9 in position 7: ordinal not in range(128) 这是 ...

  9. ActiveMQ教程(消息发送和接受)

    一 环境的搭建 version为你的版本号 如果你是普通的项目的话,创建一个lib文件夹,导入相应的jar包到你的lib中,jar包为:activemq-all-{version}.jar.log4j ...

  10. Spring入门2. IoC中装配Bean

    Spring入门2. IoC中装配Bean 20131125 前言: 上一节学习了Spring在JavaProject中的配置,通过配置文件利用BeanFactory和ApplicationConte ...