-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path可空值类型的实现方式
77 lines (40 loc) · 3.97 KB
/
可空值类型的实现方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
1. 问题:数据库中定义了一个可空,类型为int的字段,在代码中怎么接收和处理这种字段?
针对于这个问题,我们知道C#值类型不可能为null,如果数据库中的值为null怎么处理呢?
C#中提供了一种类似于int? 这种方式,假如我们自己实现会怎么做呢?
2. 表示空值的一些方案:
(1) 利用引用类型来表示值类型
如我们所知,C#语言中的所有类型(引用类型和值类型)都是自System.Object类派生而来,虽然值类型不能为null,但是System.Object类却可以为null,因此在所有使用值类型同时有可能需要值类型表示空值的地方使用System.Object类来代替,便可以直接使用null来表示空值了
这种方式简单粗暴,这样做肯定要借助装箱拆箱,那么这种做法肯定会引起性能的损耗。
改进版:封装一个带有值类型的类,将值类型封装到引用类型中。
这种方式肯定要比上面的稍微好点,减少了装箱拆箱。
(2) 利用标志位来判别是否为null
· 封装一个带有标志位的"可空值类型的类"
封装一个类,使用额外的bool型变量作为一个标识,来判定对应的值类型实例是否是空值。
这个时候有个不好的地方就是需要维护两个变量(一个值和一个标识),这时候这两个变量关联性很强,很容易弄出些bug。
此外类是存放在堆上的,在取值的时候比存放在栈上的值类型要慢一些。
· 封装一个带有标志位的"可空值类型的结构"
使用结构(struct)将一个bool类型的标识符和值类型封装成一个新的值类型。
struct可以参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/struct
这个时候这种新类型是值类型,在取值的时候要比上面一种要快些。
(3) 舍弃某个特定值做为null标志
实际上就是人为决定某个值为null,凡是碰到这个值得时候就视为null。
这种方式有很大局限性,并且还有舍弃一个值,是在不是个好方法,建议不要使用。
3. C#中的可空值类型(System.Nullable<T>)
为了解决以上问题CLR中引入了可空值类型(Nullable)的这种概念。
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/nullable-types/index
看下源码比较一下上面的几种方式和System.Nullable<T>
很显然,此时Nullable<T>本身是值类型,所以创建出的实例仍然是在栈上面的,此外可以看见T被约束为值类型的了。
问题:
下边这种写法会报错吗,为什么?:
Nullable<Int32> y = null;
Int32 value = y.GetValueOrDefault();
为什么一个空的对象调用一个方法没有抛出NullReferenceException??
这点可以从IL代码看出点信息:
从code和IL可以看出,在IL中
Nullable<Int32> y = null;
Nullable<Int32> z = new Nullable<Int32>();
是等价的,就是说这个时候y实际上是已经初始化了的,所以调用方法也是没什么问题的。
可空值类型的装箱和拆箱
可空值类型的装箱:如果可空值类型有值则会被装箱成T的一个已经装箱的值,如果没有值则会被装箱为空引用;
可空值类型的拆箱:如果要将已经装箱的值进行拆箱操作,那么该值可以被拆箱成为普通类型或者是拆箱成为对应的可空值类型,换句话说,要么拆箱为T,要么拆箱成Nullable<T>。要注意的一点是,在对一个空引用进行拆箱操作时,如果要将它拆箱成普通的值类型T,则运行时会抛出一个NullReferenceException异常,这是因为普通的值类型是没有空值的概念的
4. 总结:当现有的数据类型不支持我们所需要的数据类型的时候,可以用过封装各种数据类型来迎合需求。System.Nullable<T>是一种特殊的值类型,也是通过封装值类型来实现的。总之一定要寻求一种最优的实现方式。