• 《CLR via C#》笔记——可空值类型


    一,可空值类型

      顾名思义,这种类型支持null值。在FCL中被定义为System.Nullable<T>类型。这是一个结构体类型(struct),所以,不要认为System.Nullable<T>是一个引用类型,它仍然是一个值类型

    要使用可空值类型,在代码中可以这样写:

                Nullable<Int32> x = 10;
                Nullable<Int32> y = null;
                Console.WriteLine("x: HasValue={0} Value={1}", x.HasValue, x.Value);
                Console.WriteLine("y: HasValue={0} Value={1}", y.HasValue, y.GetValueOrDefault());

    得到的输出如下:

    x: HasValue=True Value=10
    y: HasValue=False Value=0

    二,C#对可空值类型的支持

        C#开发团队希望将可空值类型集成到C#语言中,为此,C#提供了一个更简单清晰的语法来处理可空值类型。可以使用类型加?号的方式来表示一个可空值类型。如下:

                Int32? x = 10;
                Int32? y = null;

    在C#中,Int32?等价于System.Nullable<Int32>。但是C#在此基础上更进一步,允许开发人员在可空类型上执行转换和转型。转换:如Int32? ⇒ Int32。转型:如Int32? ⇒ Double?。

                Int32? a = 5; //从非可空Int32转换成Nullable<Int32>
                Int32? b = null; //从‘null’隐式转换为Nullable<Int32>
                Int32 c = (Int32)a; //从Nullable<Int32>显示转换为Int32
    
                Double? d = 5; //从Int32转型为Nullable<Double>(d是double值5.0)
                Double? e = b; //从Nullable<Int32>转型为Nullable<Double>(e是null)

    C#还允许向可空实例应用操作符。下面是一些例子:

                Int32? a = 5;
                Int32? b = null;
    
                //一元操作符(+ ++ - -- ! ~)
                a++;    //a = 6
                b = -b; //b = null
    
                //二元操作符(+ - * / % & | ^ << >>)
                a = a + 3;  //a = 9
                b = b * 3; //b = null
                Int32 c = (Int32)a;
    
                //相等操作符(== !=)
                if (a == null) { /* no */ } else { /* yes */}
                if (b == null) { /* yes */ } else { /* no */}
                if (a != null) { /* yes */}
    
                //比较操作符(< > <= >=)
                if (a < b) { /* no */ } else { /* yes */}

    ●一元操作符(+,++,-,--,!,~) 操作数是null,结果就是null。

    ●二元操作符(+,-,*,/,%,&,|,^,>>,<<) 两个操作数中任何一个是null,结果就是null。但有一个例外,它发生在&和|操作符应用于Boolean?操作数时。在这种情况下,这两个操作符的行为和SQL的三值逻辑是一致的。(为了便于理解,可以将这里的null理解为UnKnown,一个非null操作数遇到一个null(UnKnown)操作数,具体要看两个数能不能确定一个值,如果不能确定就是null(UnKnown)。比如A是true,B是null,那么A&B的结果是什么呢?由于B是不确定的,可以是false或true,那么A&B的结果可能是false,也可能是true,这样一来,A&B的结果就被认为是不确定的,于是就是null。而A|B的结果,不管B是false还是true,由于A是true,可以推断出A|B的结果就是True。)

    ●相等符操作符(==,!=)两个操作符都是null,两者相等。一个操作数是null,则两者不相等。两个操作数都不是null,就比较值来判断是否相等。

    ●关系操作符(<,>,<=,>=) 两个操作符任何一个是null,结果就是false。两个操作数都不是null,就比较值。

    三,C#的空接合操作符(??

        C#提供了一个所谓的“空接合操作符”(null-coalescing operator),即??操作符,它要获取两个操作数。假如左边的操作数不是null,就返回这个操作数的值。如果左边的操作数是null,就返回右边这个操作数的值。利用空接合操作符可以方便的设置变量的默认值。它的另一个妙处在于,它既能用于引用类型,也能用于可空值类型。下面的代码演示了这种用法:

                Int32? b = null;
    
                Int32? x = b ?? 123;
                //上面折行等价于
                //x = b.HasValue ? b.Value : 123;
    
                string fileName = GetFileName() ?? "untitled";
                //上面这行等价于
                //string temp = GetFileName();
                //fileName = temp != null ? temp : "untitled";  

    有人认为??操作符只不过是三元操作符?:的“语法糖”而已。事实上,??操作符提供了重大的语法上的改进,第一个改进就是更好的支持表达式。

    Func<string> f = () => SomeMethod() ?? "unknown";

    相比下一行代码,上述代码更容易理解,下面的代码要求进行变量赋值,而且一个语句还搞不定:

                Func<string> f = () =>
                {
                    var temp = SomeMethod();
                    return temp != null ? temp : "unknown";
                };

    第二个改进是??在复合的情形中更好用。例如:

    string s = SomeMethod1() ?? SomeMethod2() ?? "unknown";

    它比下面这一堆代码更容易阅读和理解。

                string s;
                var sm1 = SomeMethod1();
                if (sm1 != null) s = sm1;
                else
                {
                    var sm2 = SomeMethod2();
                    if (sm2 != null) s = sm2;
                    else
                        s = "unknown";
                }

    四,CLR对可空值类型的特殊支持

    4.1,可空值类型的装箱

    CLR对一个Nullable<T>实例装箱时,他会检查它是否为null。如果是,则不实际装箱任何东西,并返回null值。如果实例不为null,CLR从可空实例中取出值,并对其进行装箱。

                Int32? n = null;
                object o = n; //o为null
                Console.WriteLine("o is null = {0}", o == null);//true
                n = 5;
                o = n; //o引用了一个已装箱的Int32
                Console.WriteLine("o' type {0}", o.GetType());//"System.Int32"

    4.2,可空值类型的拆箱

    CLR允许将一个已装箱的值类型T拆箱成为一个T或者一个Nullable<T>。如果对已装箱的类型的引用是null,而且要把它拆箱为一个Nullable<T>,那么CLR会将Nullable<T>的值设为null。下面的代码进行了演示:

                object o = 5;
                //它拆箱为一个Nullable<Int32>和一个Int32
                Int32? a = (Int32?)o;
                Int32 b = (Int32)o;
    
                //创建初始化为null的一个引用
                o = null;
                //把它拆箱为一个Nullable<Int32>和一个Int32
                a = (Int32?)o; // a = null
                b = (Int32)o;  //NullReferenceException

    4.3,通过可空值类型调用GetType

    在一个Nullable<T>对象上调用GetType时,CLR实际会“撒谎”说类型是T,而不是Nullable<T>。下面的代码演示了这一行为:

                Int32 x = 5;
                //下面这行会显示“System.Int32”,而非“System.Nullable<Int32>
                Console.WriteLine(x.GetType());

    4.4,通过可空值类型调用接口

    下面的代码,将一个Nullable<Int32>类型的变量转型为一个IComparable<Int32>接口类型,但Nullable<T>不像Int32那样实现了IComparable<Int32>接口。C#编译器允许这样的代码通过编译,而且CLR的验证器认为这是可以验证的,从而允许我们使用一种更简洁的语法:

                Int32? a = 5;
                Int32 result = ((IComparable)a).CompareTo(5);//能通过编译和运行
                Console.WriteLine(result);//0

    假如CLR没有提供这个支持,就要写非常繁琐的代码。首先必须转型成Int32,再转型成接口以发出调用:

    Int32 result = ((IComparable)(Int32)a).CompareTo(5);//这太繁琐了

    4.5,语法糖陷阱

                int? b = null;
                int? c = new int?();

    上面两句代码是等价的,int? b = null;被编译后会变成int? b = new int?();

    int? 可空类型是值类型,这里的null值并不等同于引用类型的null,这里要特别的注意。

    可空类型null值的比较只是编译器帮我们做了额外的工作,不要被它欺骗了。如下面的判断:

                if (a == null)
                { }
                if (a.HasValue)
                { }

    不要天真的以为a==null这句和我们平常写的一个对象为null的判断是等价的,那你就错了。

    真实的情况是,上面的代码被编译后被翻译成了:

        if (!a.HasValue)
        {
        }
        if (a.HasValue)
        {
        }

    a==null被编译成了a.HasValue。这也就解释了为什么引用类型的变量为null时,不能够调用其成员,会报一个NullReferenceException的异常。

    而可空类型即使你对它赋值为null,也可以调用它的成员,如a.HasValue,a.Equals等方法。

    这全是C#的编译器的语法糖。可空类型是值类型,它永远会有一个结构,而不会为空引用null。

  • 相关阅读:
    Oracle中的exist和in
    oracle恢复误删数据
    【axios】API 说明
    Content-type对照表
    【gdal】创建GeoTiff栅格数据
    NPM使用
    【nodejs】request 和 response 对象
    【nodejs】express框架+mysql后台数据查询
    webapp网络定位
    JS对象创建的几种方法
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/2601559.html
Copyright © 2020-2023  润新知