• 细说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


    如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!

    欢迎各位转载,转载文章之后必须在文章页面明显位置给出作者和原文连接。

  • 相关阅读:
    计算机执行程序代码的过程分析
    iOS 操作系统架构
    ios 概况了解
    android ApplicationContext Context Activity 内存的一些学习
    android 内存优化一
    android 学习
    ios 基础学习二
    ios 集合总结
    线程之间的通讯---SynchronizationContext
    ASP.NET Core 身份验证及鉴权
  • 原文地址:https://www.cnblogs.com/tdfblog/p/Nullable-Types-in-Csharp-Net.html
Copyright © 2020-2023  润新知