zoukankan      html  css  js  c++  java
  • 从 “x is null 和 x == null” 的区别看 C# 7 模式匹配中常量和 null 的匹配

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

    参考资料

  • 相关阅读:
    再看机器学习
    普通MLP处理图像时遇到了什么样的问题,才导致后续各种模型的出现
    图像分类算法为什么有那么多?
    算法的时间复杂度到底怎么算?
    [Python]7种基础排序算法-Python实现
    [Python3]星号*的打开方式
    [Pyspark]RDD常用方法总结
    [Python3]为什么map比for循环快
    Sass简介
    最全的DOM事件笔记
  • 原文地址:https://www.cnblogs.com/walterlv/p/10236526.html
Copyright © 2011-2022 走看看