服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 可空类型Nullable<T>用法详解

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

2023-02-08 14:18Sweet-Tang C#

本文详细讲解了可空类型Nullable<T>的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、简介

众所周知,值类型变量不能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
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
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。

示例:

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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>实例的隐式转换和转换。如下示例演示:

?
1
2
3
4
5
6
7
8
9
10
11
12
// 从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,则比较该值。

见下面的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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>也可以像引用类型一样,支持三元操作符。

?
1
2
3
4
5
6
// 如果雇员的年龄返回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)。 

?
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
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>。见下面的例子:

?
1
2
3
4
5
6
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()。 见下面的例子:

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

七、System.Nullable帮助类

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

?
1
2
3
4
5
6
7
8
9
10
11
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。 

?
1
2
3
4
5
6
7
8
9
10
11
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>提供了丰富的语法糖来减少开发员的工作量,下面是我想到供您参考。

简写

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

编译后的语句

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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>用法详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://www.cnblogs.com/tdfblog/p/Nullable-Types-in-Csharp-Net.html

延伸 · 阅读

精彩推荐
  • C#c#在WebAPI使用Session的方法

    c#在WebAPI使用Session的方法

    这篇文章主要介绍了c#在WebAPI使用Session的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    天涯过者11012022-02-25
  • C#C#访问及调用类中私有成员与方法示例代码

    C#访问及调用类中私有成员与方法示例代码

    访问一个类的私有成员不是什么好做法,大家也都知道私有成员在外部是不能被访问的,这篇文章主要给大家介绍了关于C#访问及调用类中私有成员与方法...

    cnc4742022-02-24
  • C#WPF中的ListBox实现按块显示元素的方法

    WPF中的ListBox实现按块显示元素的方法

    这篇文章主要介绍了WPF中的ListBox实现按块显示元素的方法,涉及ListBox属性设置相关操作技巧,需要的朋友可以参考下...

    永远爱好写程序8842021-12-07
  • C#C#获取U盘序列号的方法

    C#获取U盘序列号的方法

    这篇文章主要介绍了C#获取U盘序列号的方法,涉及C#针对硬件底层信息操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    蓝图12562021-10-28
  • C#.NET C#利用ZXing生成、识别二维码/条形码

    .NET C#利用ZXing生成、识别二维码/条形码

    ZXing是一个开放源码的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的端口。这篇文章主要给大家介绍了.NET C#利用ZXing生成、识...

    C#教程网9882021-12-14
  • C#C#设置Word文档背景的三种方法(纯色/渐变/图片背景)

    C#设置Word文档背景的三种方法(纯色/渐变/图片背景)

    本文给大家分享三种添加Word文档背景的方法,非常不错,代码简单易懂,具有参考借鉴价值,需要的朋友参考下吧...

    E-iceblue10932022-02-21
  • C#C#用递归算法解决经典背包问题

    C#用递归算法解决经典背包问题

    背包问题有好多版本,本文只研究0/1版本,即对一个物体要么选用,要么就抛弃,不能将一个物体再继续细分的情况。...

    张玉彬9182021-11-25
  • C#C#基础知识之GetType与typeof的区别小结

    C#基础知识之GetType与typeof的区别小结

    在比较对象时,需要了解他们的类型,才能决定他们的值是否能比较。所有的类都从System.Object中继承了GetType()方法,常常与typeo()运算符一起使用。这篇文...

    陈大宝5682022-11-21