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。如果Tstring,则实际签名M将是M<string>([NullableAttribute] T t),但如果T是a int,那么M将是M<int>(Nullable<int> t)。这两个签名根本不同,而且这种差异是不可调和的。

由于可空引用类型和可空值类型的具体表示之间存在此问题,因此任何使用都T?必须要求您将其约束Tclass或者struct

posted @ 2021-09-25 16:54  小林野夫  阅读(225)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/