先展示一下源码:
源码地址:https://referencesource.microsoft.com/#mscorlib/system/nullable.cs,ffebe438fd9cbf0e
public struct Nullable<T> where T : struct
{
private bool hasValue;
internal T value;
[System.Runtime.Versioning.NonVersionable]
public Nullable(T value) {
this.value = value;
this.hasValue = true;
}
public bool HasValue {
[System.Runtime.Versioning.NonVersionable]
get {
return hasValue;
}
}
public T Value {
get {
if (!hasValue) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
}
return value;
}
}
[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault() {
return value;
}
[System.Runtime.Versioning.NonVersionable]
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() : "";
}
[System.Runtime.Versioning.NonVersionable]
public static implicit operator Nullable<T>(T value) {
return new Nullable<T>(value);
}
[System.Runtime.Versioning.NonVersionable]
public static explicit operator T(Nullable<T> value) {
return value.Value;
}
}
根据上面代码,可以看到 Nullable模板也是一个结构体,值类型,也是轻量级的。只不过增加了一个bool hasValue,这个值在构造结构体的时候设置为true。之后如果获取value的话,如果没构造,就会抛出异常。
因此,在代码中构造一个可空的int,可以写:
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Nullable<int> x = 5;
Nullable<int> y = null;
Console.WriteLine($"x hasvalue : {x.HasValue} Value: {x.Value}");
Console.WriteLine($"y hasvalue : {y.HasValue} Value: {y.GetValueOrDefault()}");
}
}
}
结果如下:
虽然声明一个Nullable模板可以做到可空类型,但是官方还是觉得写起来麻烦,所以c# 2.0以后就在语言层面添加了对可空类型的支持。
C#允许使用?表示法来声明:
int? x1 = 5;
int? y1 = null;
在c#中 int? 就等于 Nullable< T >
允许向可空实例应用操作符:
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
int? x = 5;
int? y = null;
//一元
x++;
y = -y;
//二元
x += 3;
y += 3;
//相等性
if (x == y)
{
}
//比较
if (x < y)
{
}
}
}
}
所有运算中,只要一个值为null 那么结果就是null
c#土工了一个“空接合操作符( null - coalescing operator ),即??操作符,它要获取两个操作数。假如左边的操作数不为 nul ,就返回这个操作数的值。如果左边的操作数为 null ,就返回右边的操作数的值。利用空接合操作符,可以方便地设置变量的默认值。
空接操作符的一个好处在于,它既能用于引用类型,也能用于可空值类型。以下代码演示了如何使用??操作符:
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
int? x = null;
int? y = x.HasValue ? x : 123;
int? z = x ?? 123;
}
}
}
上面代码中,z的初始化,等价于y的初始化,但是更简便。
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
int? x = null;
object o = x;
Console.WriteLine($"o is null ? {o == null}");
x = 5;
o = x;
Console.WriteLine($"o is null ? {o == null}");
}
}
}
从上面结果可以看到装箱的时候需要看可空类型是不是为null,如果为空不会发生装箱操作,object仍让是null。
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
object o = 5;
int? x = (int?)o; // 5
int y = (int)o; // 5
Console.WriteLine($"x: {x.Value} y {y}");
o = null;
int? x1 = (int?)o; // null
int y1 = (int)o; //System.NullReferenceException:“Object reference not set to an instance of an object.”
Console.WriteLine($"x1: {x1.Value} y1 {y1}");
}
}
}
从上面代码可以看出来,拆箱对于非null对象可以转成 int 或者 int?,但是对于null对象,如果转成int则会抛出异常。
在Nullable< T >对象上调用GetType()方法,CLR实际上会撒谎说类型是T,而不是Nullable< T >。
因此运行如下代码:
int? x = 5;
Console.WriteLine($"x.GetType() : {x.GetType()}");
结果:
可空类型,一方面兼容了一些业务对null值类型的需求,一方面提高了代码的简练性。因此其实可以在写代码的时候适当的习惯使用。