Keep on going never give up.

Let's Go

C# 学习笔记(56)可空类型

C#Lonely2019-06-15 19:00:4680次0条

可空类型也是值类型,它是包含null值的值类型。例如:int? num=null; int?就是可空的int类型。“?”修饰符只是C#提供的一个语法糖,所谓语法糖,就是C#提供的一种方便的表示形式。C#中肯定没有int?这个类型,对于编译器而言,int?会被编译成Nullable<int>类型,即可空类型。C#2.0中提供的可空类型是Nullable<T>和Nullable(这个T就是泛型参数,由于可空类型的定义是public struct Nullable<T> where T:struct,T只能为值类型)。

Nullable<int> value = null;
//上面的代码也可以这样定义
int? num = 500;
//HasValue属性指示可空对象是否有值
Console.WriteLine(value.HasValue); //false
Console.WriteLine(num.HasValue); //true

//GetValueOrDefault() 代表如果可空对象有值,就用它的值返回;如果可空对象不包含值,则返回默认值0
Console.WriteLine(value.GetValueOrDefault()); //输出0
Console.WriteLine(num.GetValueOrDefault()); //输出500

//GetValueOrDefault(T) 方法代表如果HasValue属性为true,则返回value属性的值,否则返回为参数的值
Console.WriteLine(value.GetValueOrDefault(200)); //输出了200

//GetHashCode() 代表如果HasValue属性为true,则value属性返回对象的哈希代码,如果HasValue属性为false,则返回0
Console.WriteLine(num.GetHashCode()); //输出了500
Console.ReadKey();

前面我们讲到,可空类型也是值类型,这个结论是有理有据的,其依据就是可空类型的IL代码,如下图:

image.png


空合并操作符

空合并操作符即??操作符,它会对左右两个操作数进行判断:如果左边的数不为null,就返回左边的数;如果左边的数为null,就返回右边的数。这个操作符可以用于可空类型和引用类型,但不能用于值类型。(因为??运算符会将其左边的数与null进行比较,但除了可空类型外,其他的值类型都是不能与null类型进行比较的,所以??运算符不能应用与值类型)。

Nullable<int> n1 = null;          
int? n2 = 500;
Console.WriteLine(n1 ?? n2); //输出了500
// ??与三元运算符功能差不多
int n = n1.HasValue ? n1.Value : 500; //n为500

//同时??运算符也可以用于引用类型
string s = "你不是真正的快乐";
string str = null;
Console.WriteLine(s??str);

//上面代码等价于:
Console.WriteLine(s != null ? s : str);
//也等价于:(伪码)
//if (s != null)
//    return s;
//else
//    return str;
Console.ReadKey();

从以上代码可以看出,使用??运算符可以很方便地设置默认值,避免了通过if和else语句来判断,从而简化了代码行数,提高了代码的可读性。


可空类型的装箱和拆箱操作

既然值类型存在装箱和拆箱的过程,而可空类型属于值类型,那么它自然也就存在装箱和拆箱的操作了。下面我们一起来看看可空类型的装箱和拆箱的过程。

当一个可空类型赋值给引用类型变量时,CLR会对可空类型(Nullable<T>)对象进行装箱处理。CLR首先会检测可空类型是否为null,如果为null,CLR将不会进行实际的装箱操作(因为null可以赋值给一个引用类型变量);如果不为null,CLR则从可空类型对象中获取值,并对该值进行装箱(即值类型的装箱过程)。

当把一个已装箱的值类型赋值给可空类型变量时,CLR会对已装箱的值类型进行拆箱处理。如果已装箱值类型的引用为null,则CLR会把可空类型也设为null。

Nullable<int> n1 = null;
int? n2 = 500;
//获得可空对象的类型,此时返回System.Int32,而不是System.Nullable<System.Int32>,这点注意
Console.WriteLine("获取不为null的可空对象的类型:" + n2.GetType()); //输出System.Int32
//对一个为null的类型调用方法时将出现异常,所以一般对引用类型调用方法前,最好先检测下它是否为null
//Console.WriteLine("获取为null的可空对象的类型:" + n1.GetType()); //这句将报异常

//将不为null的可空类型对象赋值给引用类型,此时发生装箱操作,可通过IL证明
object obj = n2;
Console.WriteLine("获得装箱后引用类型的类型:" + obj.GetType()); //输出System.Int32

//拆箱成非可空变量
int num = (int)obj;
Console.WriteLine(num); //500

//拆箱成可空变量
n1 = (int?)obj;
Console.WriteLine(n1); //500

int? n3 = null;
obj = n3; //对一个没有值的可空类型对象进行装箱操作
Console.WriteLine(obj == null); //true

n3 = (int?)obj; //拆箱成可空变量
Console.WriteLine(n3 == null); //true

//拆箱成非可空变量,此时会抛异常,因为没有值的可空类型装箱后obj等于null,引用了一个空地址,相当于拆箱后把null赋值给一个int类型的变量,所以会抛异常
//int n4 = (int)obj;

Console.ReadKey();

对于可空类型,我们需要注意的地方有以下几点:

1、通过GetType方法来获取赋值的可空类型时,返回的将是赋值的类型。

2、对已赋值的可空类型装箱后,如果使用GetTpye函数去获取装箱后的引用类型,输出的将仍然是已赋值的的类型。

3、如果把一个没有值的可空类型装箱之后再拆箱,不能拆箱为非可空类型的值类型,否则会抛出NullReferenceException异常。

最后还有一点,需要特别注意,对于没有值的可空类型调用函数时会抛出引用异常,那么为什么没有值的可空类型仍然可以访问HasValue属性呢?

原因在于,可空类型是包含null值的可空类型,对于向可空类型赋值这项操作来说,null是一个有效的值类型。而向引用类型赋值null值则表示为空引用,表示不会指向托管堆中的任何对象,所以可以访问HasValue属性。没有值的可空类型在调用GetType函数前,编译器会对可空类型进行装箱操作,使其变为null,即空引用。所以之后再调用GetType函数时,就会抛出空引用异常了。


暗锚,解决锚点偏移

文章评论

    嘿,来试试登录吧!