可空类型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>类型的逻辑定义:

 namespace System
 {
     [Serializable]
     public struct Nullable<T> where T : struct
     {
         private bool hasValue;
         internal T value;

         public Nullable(T value) {
             this.value = value;
             this.hasValue = true;
         }

         public bool HasValue {
             get {
                 return hasValue;
             }
         }

         public T Value {
             get {
                 if (!HasValue) {
                     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
                 }
                 return value;
             }
         }

         public T GetValueOrDefault() {
             return value;
         } 

         public T GetValueOrDefault(T defaultValue) {
             return HasValue ? value : defaultValue;
         } 

         public override bool Equals(object other) {
             if (!HasValue) return other == null;
             if (other == null) return false;
             return value.Equals(other);
         }

         public override int GetHashCode() {
             return HasValue ? value.GetHashCode() : 0;
         }

         public override string ToString() {
             return HasValue ? value.ToString() : "";
         } 

         public static implicit operator Nullable<T>(T value) {
             return new Nullable<T>(value);
         } 

         public static explicit operator T(Nullable<T> value) {
             return value.Value;
         }
     }
 }

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

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

二、语法和用法

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

示例:

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

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

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

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

     Nullable<int> i = 1;
     Nullable<int> j = null;

     Console.WriteLine(i.HasValue);
     //输出结果:True

     Console.WriteLine(i.Value);
     //输出结果:1

     Console.WriteLine(j.HasValue);
     //输出结果:False

     Console.WriteLine(j.Value);
     //抛异常: System.InvalidOperationException

三、类型的转换和运算

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

     // 从System.Int32隐式转换为Nullable<Int32>
     int? i = 5;

     // 从'null'隐式转换为Nullable<Int32>
     int? j = null;

     // 从Nullable<Int32>到Int32的显式转换
     int k = (int)i;

     // 基础类型之间的转换
     Double? x = 5; // 从Int到Nullable<Double> 的隐式转换
     Double? y = j; // 从Nullable<Int32> 隐式转换Nullable<Double>

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

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

见下面的例子:

     int? i = 5;
     int? j = null;

     // 一元运算符
     i++; // i = 6
     j = -j; // j = null

     // 二元运算符
     i = i + 3; // i = 9
     j = j * 3; // j = null;

     // 等号运算符(==、!=)
     var r = i == null; //r = false
     r = j == null; //r = true
     r = i != j; //r = true

     // 比较运算符(<、>、<=、>=)
     r = i > j; //r = false

     i = null;
     r = i >= j; //r = false,注意,i=null、j=null,但是>=返回的结果是false

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

     // 如果雇员的年龄返回null(出生日期可能未输入),请设置值0.
     int age = employee.Age ?? 0;

     // 在聚合函数中使用三元操作符。
     int?[] numbers = {};
     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)。

     int? n = null;
     object o = n; //不会进行装箱操作,直接返回null值

     Console.WriteLine("o is null = {0}", object.ReferenceEquals(o, null));
     //输出结果:o is null = True

     n = 5;
     o = n; //o引用一个已装箱的Int32

     Console.WriteLine("o's type = {0}", o.GetType());
     //输出结果:o's type = System.Int32

     o = 5;

     //将Int32类型拆箱为Nullable<Int32>类型
     int? a = (Int32?)o; // a = 5
     //将Int32类型拆箱为Int32类型
     int b = (Int32)o; // b = 5

     // 创建一个初始化为null
     o = null;
     // 将null变为Nullable<Int32>类型
     a = (Int32?)o; // a = null
     b = (Int32)o; // 抛出异常:NullReferenceException

五、GetType()方法

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

     int? i = 10;
     Console.WriteLine(i.GetType());
     //输出结果是:System.Int32

     i = null;
     Console.WriteLine(i.GetType()); //NullReferenceException

原因分析:

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

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

六、ToString()方法

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

     int? i = 10;
     Console.WriteLine(i.ToString());
     //输出结果:10

     i = null;
     Console.WriteLine(i.ToString() == string.Empty);
     //输出结果:True

七、System.Nullable帮助类

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

 public static class Nullable
 {
     //返回指定的可空类型的基础类型参数。
     public static Type GetUnderlyingType(Type nullableType);

     //比较两个相对值 System.Nullable<T> 对象。
     public static int Compare<T>(T? n1, T? n2) where T : struct

     //指示两个指定 System.Nullable<T> 对象是否相等。
     public static bool Equals<T>(T? n1, T? n2) where T : struct
 }

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

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

     Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<int>)));
     //输出结果:System.Int32

     Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null);
     //输出结果:True

     Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null);
     //输出结果:True

     Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null);
     //输出结果:True

八、语法糖

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

简写

    int? i = 5;

    int? j = null;

    var r = i != null;

    var v = (int) i;

    i++;

    i = i + 3;

    r = i != j;

    r = i >= j;

    var k = i + j;

    double? x = 5;

    double? y = j;

编译后的语句

    int? i = new int?(5);

    int? j = new int?();

    var r = i.HasValue;

    var v = i.Value;

    i = i.HasValue ? new int?(i.GetValueOrDefault() + 1) : new int?();

    i = i.HasValue ? new int?(i.GetValueOrDefault() + 3) : new int?();

    r = i.GetValueOrDefault() != j.GetValueOrDefault() || i.HasValue != j.HasValue;

    r = i.GetValueOrDefault() >= j.GetValueOrDefault() && i.HasValue & j.HasValue;

    int? k = i.HasValue & j.HasValue ? new int?(i.GetValueOrDefault() + j.GetValueOrDefault()) : new int?();

    double? x = new double?((double) 5);

    double? y = j.HasValue ? new double?((double) j.GetValueOrDefault()) : new double?();

到此这篇关于可空类型Nullable<T>用法详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#难点逐个击破(8):可空类型System.Nullable

    null与void null值用来表示数据类型未被赋予任何值,它是一种引用类型:void表示没有类型,或者说是没有任何值.null与void的区别可以认为void是根本没有,而null是一个空箱子,里面什么都没有. null值只能赋值给引用类型,这里注意到string也是一种引用类型:引用类型在C中称为"指针",即存放变量位置的内存空间位置.将变量设为null,会显式地设置引用,且它本身不指向任何内存位置: null值赋给值类型将导致编译错误. void用于方法值的返回,其本质并不是一

  • .NET中可空值类型【Nullable<T>】实现原理

    为了让.Net中的值类型可以赋值为null,微软特地添加了Nullable<T>类型,也可简写为T?.但是Nullable<T>自身是结构体,也是值类型,那么它是如何实现将null赋值给值类型的呢? 下面通过自定义一个可空值类型来讲解Nullable<T>的实现原理. 自定义可空值类型 struct XfhNullable<T> where T : struct { private T innerValue; //这个属性很重要 public bool Ha

  • 详解c# 可空类型(Nullable)

    C# 单问号 ? 与 双问号 ?? ? : 单问号用于对 int,double,bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,意思是这个数据类型是 NullAble 类型的. int? i = 3 等同于 Nullable<int> i = new Nullable<int>(3); int i; //默认值0 int? ii; //默认值null ?? : 双问号 可用于判断一个变量在为 null 时返回一个指定的值. 接下来我们详细说明. C# 可空类

  • 可空类型Nullable<T>用法详解

    目录 一.简介 二.语法和用法 三.类型的转换和运算 四.装箱与拆箱 五.GetType()方法 六.ToString()方法 七.System.Nullable帮助类 八.语法糖 一.简介 众所周知,值类型变量不能null,这也是为什么它们被称为值类型.但是,在实际的开发过程中,也需要值为null的一些场景.例如以下场景: 场景1:您从数据库表中检索可空的整数数据列,数据库中的null值没有办法将此值分配给C#中Int32类型: 场景2:您在UI绑定属性,但是某些值类型的字段不是必须录入的(例

  • js判断是否为空和typeof的用法(详解)

    (1)typeof用法 typeof的运算数未定义,返回的就是 "undefined". 运算数为数字 typeof(x) = "number" 字符串 typeof(x) = "string" 布尔值 typeof(x) = "boolean" 对象,数组和null typeof(x) = "object" 函数 typeof(x) = "function" (2)js判断是否为空 v

  • iOS 泛型中nullable、null resettable、null kindof 用法详解

    iOS9新出的关键字:用来修饰属性,或者方法的参数,方法的返回值 iOS9新出关键字nonnull,nullable,null_resettable,_Null_unspecified 需要注意的一点只能修饰对象,不能修饰基本数据类型. 虽然在项目的代码编写中不会经常用到,不过在调用苹果系统方法的时候还是会经常遇到,需要做一个总结 nullable作用:表示可以为空 nullable书写规范: // 方式一: @property (nonatomic, strong, nullable) NSS

  • MySQL中建表时可空(NULL)和非空(NOT NULL)的用法详解

    对于MySQL的一些个规范,某些公司建表规范中有一项要求是所有字段非空,意味着没有值的时候存储一个默认值.其实所有字段非空这么说应该是绝对了,应该说是尽可能非空,某些情况下不可能给出一个默认值. 那么这条要求,是基于哪些考虑因素,存储空间?相关增删查改操作的性能?亦或是其他考虑?该理论到底有没有道理或者可行性,本文就个人的理解,做一个粗浅的分析. 1,基于存储的考虑 这里对存储的分析要清楚MySQL数据行的存储格式,这里直接从这篇文章白嫖一部分结论,文章里分析的非常清楚(其实也是参考<MySQL

  • C#元组类型ValueTuple用法详解

    System.Tuple 类型是在.NET 4.0中引入的,但是有两个明显的缺点:(1) Tuple 类型是引用类型.(2) 没有构造函数支持. 为了解决这些问题,C# 7 引入了新的语言功能以及新的类型. 现在,如果您需要从函数中返回两个值的合并结果,或者把两个值合并到一个哈希表中,可以使用System.ValueTuple类型并使用一个精短的语法来构造它们: // 构建元组实例 var tpl = (1, 2); // 在字典中使用元组 var d = new Dictionary<(int

  • Android Studio 3.6中新的视图绑定工具ViewBinding 用法详解

    前言 我们在Android开发的过程中总是需要获取XML布局中的ViewId,以便给其赋值进行显示,早期我们只能使用 findViewById 这个API,会导致很多的模版代码出现.2013年左右Android界大神 Jake Wharton开源了Butter Knife框架,通过Bind("viewid")方式方便开发者获取ViewId.近两年由于谷歌对Kotlin的支持,我们开始使用 Android Kotlin extensions. 在文件中导入布局文件直接引用viewId.无

  • JSP中EL表达式的用法详解(必看篇)

    EL 全名为Expression Language EL 语法很简单,它最大的特点就是使用上很方便.接下来介绍EL主要的语法结构: ${sessionScope.user.sex} 所有EL都是以${为起始.以}为结尾的.上述EL范例的意思是:从Session的范围中,取得 用户的性别.假若依照之前JSP Scriptlet的写法如下: User user =(User)session.getAttribute("user"); String sex =user.getSex( );

  • jQuery选择器之属性筛选选择器用法详解

    在这么多属性选择器中[attr="value"]和[attr*="value"]是最实用的 [attr="value"]能帮我们定位不同类型的元素,特别是表单form元素的操作,比如说input[type="text"],input[type="checkbox"]等 [attr*="value"]能在网站中帮助我们匹配不同类型的文件 <!DOCTYPE html> <

  • java 查询oracle数据库所有表DatabaseMetaData的用法(详解)

    一 . 得到这个对象的实例 Connection con ; con = DriverManager.getConnection(url,userName,password); DatabaseMetaData dbmd = con.getMetaData(); 二. 方法getTables的用法 原型: ResultSet DatabaseMetaData.getTables(String catalog,String schema,String tableName,String []type

  • java中栈和队列的实现和API的用法(详解)

    在java中要实现栈和队列,需要用到java集合的相关知识,特别是Stack.LinkedList等相关集合类型. 一.栈的实现 栈的实现,有两个方法:一个是用java本身的集合类型Stack类型:另一个是借用LinkedList来间接实现Stack. 1.Stack实现 直接用Stack来实现非常方便,常用的api函数如下: boolean        isEmpty() // 判断当前栈是否为空 synchronized E        peek() //获得当前栈顶元素 synchro

随机推荐