细说Nullable<T>类型

目录
一、简介
二、语法和用法
三、类型的转换和运算
四、装箱与拆箱
五、GetType()方法
六、ToString()方法
七、System.Nullable帮助类
八、语法糖

一、简介

  众所周知,值类型变量不能null,这也是为什么它们被称为值类型。但是,在实际的开发过程中,也需要值为null的一些场景。例如以下场景:

  场景1:您从数据库表中检索可空的整数数据列,数据库中的null值没有办法将此值分配给C#中Int32类型;

  场景2:您在UI绑定属性,但是某些值类型的字段不是必须录入的(例如在人员管理中的死亡日期);

  场景3:在Java中,java.Util.Date是一个引用类型,因此可以将此类型的字段设置为null。但是,在CLR中,System.DateTime是一个值类型,DateTime 变量不能null。如果使用Java编写的应用程序要将日期/时间传达给在CLR上运行的Web服务,如果Java应用程序发送是null, CLR中没有供对应的类型;

  场景4:在函数中传递值类型时,如果参数的值无法提供并且不想传递,可以使用默认值。但有时默认值并不是最佳的选择,因为默认值实际也传递了一个默认的参数值,逻辑需要特殊的处理;

  场景5:当从xml或json反序列化数据时,数据源中缺少某个值类型属性的值,这种情况很不方便处理。

  当然,我们日常工作中还有很多类似的情况。

  为了摆脱这些情况,Microsoft在CLR中增加了可为空值类型的概念。为了更清楚理解这一点,我们看一下System.Nullable<T>类型的逻辑定义:

 1 namespace System
 2 {
 3     [Serializable]
 4     public struct Nullable<T> where T : struct
 5     {
 6         private bool hasValue;
 7         internal T value;
 8
 9         public Nullable(T value) {
10             this.value = value;
11             this.hasValue = true;
12         }
13
14         public bool HasValue {
15             get {
16                 return hasValue;
17             }
18         }
19
20         public T Value {
21             get {
22                 if (!HasValue) {
23                     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
24                 }
25                 return value;
26             }
27         }
28
29         public T GetValueOrDefault() {
30             return value;
31         }
32
33         public T GetValueOrDefault(T defaultValue) {
34             return HasValue ? value : defaultValue;
35         }
36
37         public override bool Equals(object other) {
38             if (!HasValue) return other == null;
39             if (other == null) return false;
40             return value.Equals(other);
41         }
42
43         public override int GetHashCode() {
44             return HasValue ? value.GetHashCode() : 0;
45         }
46
47         public override string ToString() {
48             return HasValue ? value.ToString() : "";
49         }
50
51         public static implicit operator Nullable<T>(T value) {
52             return new Nullable<T>(value);
53         }
54
55         public static explicit operator T(Nullable<T> value) {
56             return value.Value;
57         }
58     }
59 } 

查看Nullable的定义

  从上面的定义可以总结如下几点:

  • Nullable<T> 类型也是一个值类型;
  • Nullable<T> 类型包含一个Value属性用于表示基础值,还包括一个Boolean类型的HasValue属性用于表示该值是否为null
  • Nullable<T> 是一个轻量级的值类型。Nullable<T>类型的实例占用内存的大小等于一个值类型与一个Boolean类型占用内存大小之和;
  • Nullable<T> 的泛型参数T必须是值类型。您只能将Nullable<T>类型与值类型结合使用,您也可以使用用户定义的值类型。

二、语法和用法

  使用Nullable<T>类型,只需指定一个其它值类型的泛型参数T。

  示例: 

1     Nullable<int> i = 1;
2     Nullable<int> j = null;
3     Nullable<Nullable<int>> k; //这是一个错误语法,编译会报错。

  CLR还提供了一种简写的方式。

1     int? i = 1;
2     int? j = null;

  可以通过 Value 属性来获取基础类型的值。如下所示,如果不为null,则将返回实际的值,否则将抛出InvalidOperationException异常;您可以在调用Value属性的时,需要检查是否为null

 1     Nullable<int> i = 1;
 2     Nullable<int> j = null;
 3
 4     Console.WriteLine(i.HasValue);
 5     //输出结果:True
 6
 7     Console.WriteLine(i.Value);
 8     //输出结果:1
 9
10     Console.WriteLine(j.HasValue);
11     //输出结果:False
12
13     Console.WriteLine(j.Value);
14     //抛异常: System.InvalidOperationException    

三、类型的转换和运算

  C#还支持简单的语法来使用Nullable<T>类型。它还支持Nullable<T>实例的隐式转换和转换。如下示例演示:

 1     // 从System.Int32隐式转换为Nullable<Int32>
 2     int? i = 5;
 3
 4     // 从‘null‘隐式转换为Nullable<Int32>
 5     int? j = null;
 6
 7     // 从Nullable<Int32>到Int32的显式转换
 8     int k = (int)i;
 9
10     // 基础类型之间的转换
11     Double? x = 5; // 从Int到Nullable<Double> 的隐式转换
12     Double? y = j; // 从Nullable<Int32> 隐式转换Nullable<Double>

  对Nullable<T> 类型使用操作符,与包含的基础类型使用方法相同。

  • 一元运算符(++、--、 - 等),如果Nullable<T>类型值是null时,返回null
  • 二元运算符(+、-、*、/、%、^等)任何操作数是null,返回null
  • 对于==运算符,如果两个操作数都是null,则表达式计算结果为true,如果任何一个操作数是null,则表达式计算结果为false;如果两者都不为null,它照常比较。
  • 对于关系运算符(>、<、>=、<=),如果任何一个操作数是null,则运算结果是false,如果操作数都不为null,则比较该值。

  见下面的例子:  

 1     int? i = 5;
 2     int? j = null;
 3
 4     // 一元运算符
 5     i++; // i = 6
 6     j = -j; // j = null
 7
 8     // 二元运算符
 9     i = i + 3; // i = 9
10     j = j * 3; // j = null;
11
12     // 等号运算符(==、!=)
13     var r = i == null; //r = false
14     r = j == null; //r = true
15     r = i != j; //r = true
16
17     // 比较运算符(<、>、<=、>=)
18     r = i > j; //r = false
19
20     i = null;
21     r = i >= j; //r = false,注意,i=null、j=null,但是>=返回的结果是false

  Nullable<T>也可以像引用类型一样,支持三元操作符。

1     // 如果雇员的年龄返回null(出生日期可能未输入),请设置值0.
2     int age = employee.Age ?? 0;
3
4     // 在聚合函数中使用三元操作符。
5     int?[] numbers = {};
6     int total = numbers.Sum() ?? 0;

四、装箱与拆箱

  我们已经知道了Nullable<T>是一个值类型,现在我们再来聊一聊它的装箱与拆箱。
  CLR采用一个特殊的规则来处理Nullable<T>类型的装箱与拆箱。当一个Nullable<T>类型的实例装箱时,CLR会检查实例的HasValue属性:如果是true,则将实例Value属性的值进行装箱后返回结果;如果返回false,则直接返回null,不做任何的处理。
  在拆箱处理时,与装箱处反。CLR会检查拆箱的对象是否为null,如果是直接创建一个新的实例 new Nullable<T>(),如果不为null,则将对象拆箱为类型T,然后创建一个新实例 new Nullable<T>(t)。

 1     int? n = null;
 2     object o = n; //不会进行装箱操作,直接返回null值
 3
 4     Console.WriteLine("o is null = {0}", object.ReferenceEquals(o, null));
 5     //输出结果:o is null = True
 6
 7
 8     n = 5;
 9     o = n; //o引用一个已装箱的Int32
10
11     Console.WriteLine("o‘s type = {0}", o.GetType());
12     //输出结果:o‘s type = System.Int32
13
14     o = 5;
15
16     //将Int32类型拆箱为Nullable<Int32>类型
17     int? a = (Int32?)o; // a = 5
18     //将Int32类型拆箱为Int32类型
19     int b = (Int32)o; // b = 5
20
21     // 创建一个初始化为null
22     o = null;
23     // 将null变为Nullable<Int32>类型
24     a = (Int32?)o; // a = null
25     b = (Int32)o; // 抛出异常:NullReferenceException

五、GetType()方法

  当调用Nullable<T>类型的GetType()方法时,CLR实际返回类型的是泛型参数的类型。因此,您可能无法区分Nullable<Int32>实例上是一个Int32类型还是Nullable<Int32>。见下面的例子:

1     int? i = 10;
2     Console.WriteLine(i.GetType());
3     //输出结果是:System.Int32
4
5     i = null;
6     Console.WriteLine(i.GetType()); //NullReferenceException

  原因分析:

  这是因为调用GetType()方法时,已经将当前实例进行了装箱,根据上一部分装箱与拆箱的内容,这里实际上调用的是Int32类型的GetType()方法。

  调用值类型的GetType()方法时,均会产生装箱,关于这一点大家可以自己去验证。

六、ToString()方法

  当调用Nullable<T>类型的ToString()方法时,如果HasValue属性的值为false,则返回String.Empty,如果该属性的值为true,则调用的逻辑是Value.ToString()。 见下面的例子:

1     int? i = 10;
2     Console.WriteLine(i.ToString());
3     //输出结果:10
4
5     i = null;
6     Console.WriteLine(i.ToString() == string.Empty);
7     //输出结果:True

七、System.Nullable帮助类

  微软还提供一个同名System.Nullable的静态类,包括三个方法:

 1 public static class Nullable
 2 {
 3     //返回指定的可空类型的基础类型参数。
 4     public static Type GetUnderlyingType(Type nullableType);
 5
 6     //比较两个相对值 System.Nullable<T> 对象。
 7     public static int Compare<T>(T? n1, T? n2) where T : struct
 8
 9     //指示两个指定 System.Nullable<T> 对象是否相等。
10     public static bool Equals<T>(T? n1, T? n2) where T : struct
11 }

  在这里面我们重点说明一下GetUnderlyingType(Type nullableType)方法,另外两个方法是用来比较值的,大家可以自己研究。

  GetUnderlyingType(Type nullableType)方法是用来返回一个可为空类型的基础类型,如果 nullableType 参数不是一个封闭的Nullable<T>泛型,则反回null

 1     Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<int>)));
 2     //输出结果:System.Int32
 3
 4     Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null);
 5     //输出结果:True
 6
 7     Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null);
 8     //输出结果:True
 9
10     Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null);
11     //输出结果:True

八、语法糖

  微软对Nullable<T>提供了丰富的语法糖来减少开发员的工作量,下面是我想到供您参考。

简写 编译后的语句

 1     int? i = 5;
 2
 3     int? j = null;
 4
 5     var r = i != null;
 6
 7     var v = (int) i;
 8
 9     i++;
10
11     i = i + 3;
12
13     r = i != j;
14
15     r = i >= j;
16
17     var k = i + j;
18
19     double? x = 5;
20
21     double? y = j;

 1     int? i = new int?(5);
 2
 3     int? j = new int?();
 4
 5     var r = i.HasValue;
 6
 7     var v = i.Value;
 8
 9     i = i.HasValue ? new int?(i.GetValueOrDefault() + 1) : new int?();
10
11     i = i.HasValue ? new int?(i.GetValueOrDefault() + 3) : new int?();
12
13     r = i.GetValueOrDefault() != j.GetValueOrDefault() || i.HasValue != j.HasValue;
14
15     r = i.GetValueOrDefault() >= j.GetValueOrDefault() && i.HasValue & j.HasValue;
16
17     int? k = i.HasValue & j.HasValue ? new int?(i.GetValueOrDefault() + j.GetValueOrDefault()) : new int?();
18
19     double? x = new double?((double) 5);
20
21     double? y = j.HasValue ? new double?((double) j.GetValueOrDefault()) : new double?();

参考:

转载请注明出处,原文链接:http://www.cnblogs.com/tdfblog/p/Nullable-Types-in-Csharp-Net.html

时间: 2024-11-05 17:17:37

细说Nullable<T>类型的相关文章

细说可空类型 nullable PropertyType

可空类型是System.Nullable结构体的实列.一个可空类型代表了相应值类型的正确范围附加null值.这么说来,其实也不是很明子,命题嘛,一般不求易懂,但求准确. 那我就来说说这可空类型吧,上次说到了值类型与引用类型,其中就说到了,值类型是不能为空的,int i=null是错的,值类型为能为空,但我们有时候需要让值类型也可以为空,怎么办呢,那就在值类型的值的范围上扩充一个null值. 为什么要有可空类型 我们在日常开发中,什么地方需要让值类型也变成可空呢?如果你有试过把数据库内的数据对象化

可空类型

可空类型.匿名方法和迭代器这三个优美的特性是在C#2.0里面提出来的. 1.可空类型 当我们在使用数据库的时候,会发现这样的一个矛盾点:数据库的字段设置是允许为null的,比如日期的字段,当你想把数据库表映射为C#中的对象时会发现,DateTime类型在C#语言中是不能为null的! 1.1 简介 可空类型也是值类型,但它是包含null值的值类型:int? nullable=null; int?就是可空的int类型.很明显,这又是一个语法糖,肯定不会存在int?这样的类型.对于编译器而言,int

【C#进阶系列】18 可空值类型

可空值类型,正如字面意义上的,是可以为NULL的值类型. 这个东西存在的意义可以解决比如数据库的的Int可以为NUll的情况,使得处理数据库数据更简单. 实际上可空值类型就是Nullable<T>这个泛型值类型,而C#有一种更简单的语法糖是int?这种用法: Nullable<Int32> 数据库类型 = null; float? 可以为空的浮点类型 = null; DateTime? 更多的值类型 = null; 可空值类型的更多玩法 在大多数时候用C#去操作可空值类型,完全可以

谈谈Nullable&lt;T&gt;的类型转换问题

本篇文章讨论可空值类型(Nullable<T>)的转换,却确地说是如何将一种类型的值对象转换成相应的可空值.这来源于今天我们的一个成员遇到的一个小问题,我经过一些整理写了这篇文章.虽然没有什么技术含量可言,也希望对某些读者带来帮助. 目录 一.四种典型的值类型转换方式 二.当类型转换遭遇Nullable<T> 三.将基于Nullable<T>的类型转换实现在扩展方法中 四.进一步完善扩展方法ConvertTo 五.谈谈NullableTypeConverter 一.四种

C#学习笔记三: C#2.0泛型 可控类型 匿名方法和迭代器

前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭代器3个优美的特性. 1,泛型1.1 泛型是什么泛型的英文表述是"generic", 这个单词意为通用的.从字面意思可知,泛型代表的就是"通用类型",它可以代替任意的数据类型,使类型参数化,从而达到之实现一个方法就可以操作多种数据类型的目的.泛型是将方法实现行为与方法操

可空值类型(类型后面加问号)

很多时候经常遇到类型后面加问号?如int? i=null,不知道标示什么,其实这是2.0的新语法:可控制类型. 由于一个值类型变量永远不可能为null,而数据库中的一个列是允许值为空的,为了与数据库中的列相对应,CLR引入了可空值类型. C#中是以问号表示法声明并初始化可空值类型的,如下代码所示: int? x=5; int? y=null; C#允许对可空值类型进行数据转换和转型,示例代码如下: int? x=5; int z =(int)x; 可以对可空值类型进行装箱.其规则是:若可空值类型

[C#基础知识系列]专题十:全面解析可空类型[转]

原文链接 主要内容: 1:空合并操作符(?? 操作符) ??操作符也就是"空合并操作符",它代表的意思是两个操作数,如果左边的数不为null时,就返回左边的数,如果左边的数为null,就返回右边的数,这个操作符可以用于可空类型,也可以用于引用类型,但是不能用于值类型(之所以不能应用值类型(这里除了可空类型),因为??运算符要对左边的数与null进行比较,然而值类型,不能与null类型比较,所以就不支持??运算符),下面用一个例子来掩饰下??运算符的使用(??这个运算符可以方便我们设置默

C#6.0语言规范(四) 类型

C#语言的类型分为两大类:值类型和引用类型.值类型和引用类型都可以是泛型类型,它们采用一个或多个类型参数.类型参数可以指定值类型和引用类型. 1 type 2 : value_type 3 | reference_type 4 | type_parameter 5 | type_unsafe 6 ; 类型的最终类别(指针)仅在不安全的代码中可用.这在Pointer类型中进一步讨论. 值类型与引用类型的不同之处在于值类型的变量直接包含它们的数据,而引用类型的变量存储对其数据的引用,后者称为对象.对

python学习笔记1-元类__metaclass__

type 其实就是元类,type 是python 背后创建所有对象的元类 python 中的类的创建规则: 假设创建Foo 这个类 class Foo(Bar): def __init__(): pass Foo中有__metaclass__这个属性吗?如果有,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象,他是一个类,但是本身类就是对象,一个python文件模块也属于一个对象. 如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找