从 “x is null 和 x == null” 的区别看 C# 7 模式匹配中常量和 null 的匹配

从 “x is null 和 x == null” 的区别看 C# 7 模式匹配中常量和 null 的匹配

发布于 2017-11-06 15:24 更新于 2018-02-19 22:39

尝试过写 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 TestInWalterlvDemo(object value)
{
    if (value is null)
    {
    }
    if (value == null)
    {
    }
}

反编译看看:

.method private hidebysig instance void 
    TestInWalterlvDemo(
      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 TestInWalterlvDemo(object value)
{
    if (value is 1)
    {
    }
    if (value == 1)
    {
    }
}

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

private void TestInWalterlvDemo(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 很符合英语的阅读习惯,代码阅读起来比较舒适。
  • 如果是值常量,可以避免装箱带来的相等判断错误问题

参考资料

本文会经常更新,请阅读原文: https://walterlv.com/post/is-null-vs-==-null.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏C语言及其他语言

【编程经验】printf专题:你可能不知道的printf用法【文末有福利】

你可能不知道的printf用法 ? 不少小伙伴反映C语言只会用printf函数,但一些参数、返回值什么的还不了解,所以今天总结一二,给大家总结和梳理一下...

29980
来自专栏企鹅号快讯

Java与C/C加加的区别

Java 是由 C++发展而来的,保留了 C++的大部分内容,其编程方式类似于 C++。但 Java 的句法更清晰、规模更小、更易学。Sun 公司对多种程序设计...

1K60
来自专栏老马说编程

计算机程序的思维逻辑 (14) - 类的组合

上节我们通过类Point介绍了类的一些基本概念和语法,类Point中只有基本数据类型,但类中的成员变量的类型也可以是别的类,通过类的组合可以表达更为复杂的概念。...

23890
来自专栏斑斓

编程实践 | Scala亮瞎Java的眼(一)

这是我在11月15日成都OpenParty分享的一个题目,确有标题党的嫌疑。Scala自然不是无所不能,Java也没有这么差劲,我只希望给Java程序员提供另外...

36150
来自专栏『不羁阁』 | 行走少年郎专栏

OC知识--彻底理解内存管理(MRC、ARC)

37560
来自专栏加米谷大数据

多面编程语言Scala

如Scala官网宣称的:“Object-OrientedMeetsFunctional”,这一句当属对Scala最抽象的精准描述,它把近二十年间大行其道的面向对...

31240
来自专栏PPV课数据科学社区

【学习】数据分析师的Python日记-第1天:谁来给我讲讲Python?

今天带来的是PYTHON,这是一篇非常有意思的文章。希望对大家有帮助。 ---- ---- 导语:或许是网上嘈嘈杂杂的关于大数据、互联网的新形势争论,或许是招聘...

22290
来自专栏飞雪无情的博客

编写高效的Android代码

毫无疑问,基于Android平台的设备一定是嵌入式设备。现代的手持设备不仅仅是一部电话那么简单,它还是一个小型的手持电脑,但是,即使是最快的最高端的手持设备也远...

11530
来自专栏小詹同学

Leetcode打卡 | No.017 电话号码的字母组合

欢迎和小詹一起定期刷leetcode,每周一和周五更新一题,每一题都吃透,欢迎一题多解,寻找最优解!这个记录帖哪怕只有一个读者,小詹也会坚持刷下去的!

16730
来自专栏企鹅号快讯

从零基础开始学习PHP(六)

发布上一篇博文的时候、不小心忘记添加打赏功能了、这篇文章补上!如文中有误之处、还望大神指出以便改正、也可以更好的帮助后来者学习。 ? PHP中变量的类型 目标 ...

20790

扫码关注云+社区

领取腾讯云代金券