Nullable<T> 结构 |T? 可空类型
参考链接:https://www.cnblogs.com/tdfblog/p/Nullable-Types-in-Csharp-Net.html
https://www.cnblogs.com/minotauros/p/10041644.html
启用
可空上下文从C#8.0开始,我们可以通过启用可空上下文,让VS在开发过程中可以检查我们出现的空指针引用异常。
启用可空上下文的方式有两种:
1、修改.csproj文件,添加<Nullable>enable</Nullable>节点,如:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
Nullable节点的值enable表示启用,disable表示停用。
此外需要注意,这种启用方式是全局性的,修改默认行为,默认是disable,但是从.net6开始,项目默认是启用可空上下文的,项目文件.csproj中会默认包含Nullable节点。
2、使用预编译指令#nullable enable来启用可空上下文,如:
//enable表示启用
#nullable enable
//disable表示停用
#nullable disable
使用预编译指令表示局部性启用,和修改.csproj文件的方式配合使用。而#nullable enable和#nullable disable配合使用可以实现块级的局部性启用。
可为空的类型
在 C# 8.0 以后将引用类型设为默认不可为空,可以使用静态流分析。C# 8.0之前引用类型默认为空,也使用无法运行静态流分析。使用 ? 作为可为空声明,这对值类型和引用类型都适用。!表示忽略可空警告
- 可为空引用类型:string? text = null;
- 不可为空引用类型:string text = "Inigo Montoya"
我们定义一个可为空的的int 变量。用ilspy 查看一下IL代码,看看生成什么东西:
int? i = 0;
居然是泛型类型的变量。现在去docs.microsoft.com看看Nullable<T>是什么。int?
实际上是Nullable<int>
。但是string?
,它实际上是相同的,string
但有一个编译器生成的属性来注解它。这样做是为了向后兼容。换句话说,string?
是一种假象,而int?
不是。
使用方法
string text1 = null;// Warning: Cannot convert null to non-nullable reference,因为默认引用类型也是默认不可为null的所以编译器发出警告 string? text2 = null;// 正确 string moreText = text2!;//!表示:忽略警告,我作为程序更清楚该变量的使用 ,因此编译器不会在发出警告。这样一来,可以重写静态流分析,就像可以使用显式强制转换一样。当然,在运行时,仍会进行相应验证。
! 运算符(声明“相信我,我是程序员”)
Nullable<T> 结构
public struct Nullable<T> where T : struct
Object > ValueType> Nullable<T>
ValueType 是所有值类型的的基类
Nullable<T>可以简写为T? 表示可谓空的值类型
四、装箱与拆箱
我们已经知道了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()
方法时,均会产生装箱,关于这一点大家可以自己去验证,用typeof 代替get type()避免装箱。调用Oject 的方法都会产生装箱,
可空值类型和可空引用类型之间的区别
可空值类型和可空引用类型之间的区别出现在以下模式中:
1: void M<T>(T? t) where T: notnull
这意味着该参数是可以为空的,并且T
被约束为notnull
。如果T
是string
,则实际签名M
将是M<string>([NullableAttribute] T t)
,但如果T
是a int
,那么M
将是M<int>(Nullable<int> t)
。这两个签名根本不同,而且这种差异是不可调和的。
由于可空引用类型和可空值类型的具体表示之间存在此问题,因此任何使用都T?
必须要求您将其约束T
为class
或者struct
。