C# 8中的可空引用类型

  • A+
所属分类:ASP.NET C# 学习笔记

C# 8中的可空引用类型

可空引用类型?

自从我开始学习.NET, 引用类型一直就是可空的。然而初级程序员通常会告诉你值类型不可空,引用类型可空。
事实上,在.NET中有一种语法可以表明一个值类型是否可空。

  1. int? nullableInt1 = null;  
  2. Nullable<int> nullableInt2 = null;  
  3. int nullableInt3 = null//编译错误  

并且这种语法并不只适用于原始类型,它也适用于struct

Tips: Struct本身就是值类型

  1. struct MyStruct  
  2. {  
  3.    
  4. }  
  5.           
  6. static void Main(string[] args)  
  7. {  
  8.     MyStruct? mystruct1 = null;  
  9.     MyStruct myStruct2 = null;  
  10. }  

但是现在我们希望在编译以下代码时,编译器能给出错误或者警告

  1. class MyClass  
  2. {  
  3.    
  4. }  
  5.           
  6. static void Main(string[] args)  
  7. {  
  8.     MyClass myClass = null;   
  9. }  

为什么?

这里我们第一个问题就是,为什么需要让编译器给出错误或者警告?

我们接下来已一段简单的代码为例。

  1. class MyClass  
  2. {  
  3.     public void SayHello()  
  4.     {  
  5.         Console.WriteLine("Hello");  
  6.     }  
  7. }  
  8.           
  9. static void Main(string[] args)  
  10. {  
  11.     var myClass = new MyClass();   
  12.     myClass.SayHello();  
  13. }  

这个代码是某个功能的最初版本,看起来非常的简单,并且会运行的很好。

现在我们想象一下,一段之间之后,另外一个程序员加入了项目,将程序修改如下:

  1. class MyClass  
  2. {  
  3.     public void SayHello()  
  4.     {  
  5.         Console.WriteLine("Hello");  
  6.     }  
  7. }  
  8.           
  9. static void Main(string[] args)  
  10. {  
  11.     var myClass = new MyClass();  
  12.    
  13.     ...  
  14.       
  15.     if (true)    
  16.     {  
  17.         myClass = null;  
  18.     }  
  19.       
  20.     ...  
  21.    
  22.     if(myClass == null)  
  23.     {  
  24.         ...  
  25.     }  
  26.       
  27.     ...  
  28.   
  29.     myClass.SayHello();  
  30. }  

这样的代码看起来很傻,但是现实情况中确实会发生,有人会将myClass设置为null来满足他们正在处理的功能。它深藏在程序中,甚至可以通过单元测试,所有的功能看起来都运行良好。

但是在某个特定的时间点, 特定的条件下,程序会抛出一个NullReferenceException空引用异常, 这时候我们才会发现我们缺少了空引用判断,然后添加一定的防护。

  1. class MyClass  
  2. {  
  3.     public void SayHello()  
  4.     {  
  5.         Console.WriteLine("Hello");  
  6.     }  
  7. }  
  8.           
  9. static void Main(string[] args)  
  10. {  
  11.     var myClass = new MyClass();  
  12.    
  13.     ...  
  14.       
  15.     if (true)    
  16.     {  
  17.         myClass = null;  
  18.     }  
  19.       
  20.     ...  
  21.    
  22.     if(myClass == null)  
  23.     {  
  24.         ...  
  25.     }  
  26.       
  27.     ...  
  28.   
  29.     if(myClass != null)  
  30.     {  
  31.          myClass.SayHello();     
  32.     }  
  33. }  

那么如何避免其他程序员,或者未来的自己,陷入这种空引用的陷阱呢?

启用可空引用类型

如上所述,这里我们首先需要使用C#8的Nullable Reference Types功能。 完成后,只需要在项目的csproj文件中添加一行:

  1. <NullableReferenceTypes>true</NullableReferenceTypes>  

编译器产生警告

一旦我们启用了该功能,让我们看一段简单的代码来说明它是如何工作的。

  1. class MyClass  
  2. {  
  3.     public void SayHello()  
  4.     {  
  5.         Console.WriteLine("Hello");  
  6.     }  
  7. }  
  8.    
  9. static void Main(string[] args)  
  10. {  
  11.     MyClass myClass = null;  
  12.     myClass.SayHello();  
  13. }  

如果编译以上代码的话,我们会得到2个警告。这里我使用了加粗字体,是因为我们得到的只是警告,不是编译错误。你的程序依然可以编译和启动。

第一个警告是我们尝试将null分配给未明确设置为允许空值的变量。

  1. Converting null literal or possible null value to non-nullable type.  

第二个警告是当我们尝试实际使用非可空类型时,编译器认为它将为null。

  1. Possible dereference of a null reference.  

所以这两个警告都不会阻止我们的应用程序运行,但它会警告我们我们可能遇到麻烦。

下面让我们修改代码,让我们的引用类型变量可空

C# 8中可用引用类型的定义于可空值类型一样,即在声明时,类型名的后面加?号

  1. static void Main(string[] args)  
  2. {  
  3.     MyClass? myClass = null;  
  4.     myClass.SayHello();  
  5. }  

这里有趣的是,修改完代码后,编译项目,你依然会收到Possible dereference的警告。为了消除掉这个警告,你可以添加空引用检查。

  1. static void Main(string[] args)  
  2. {  
  3.     MyClass? myClass = null;  
  4.     if (myClass != null)  
  5.     {  
  6.         myClass.SayHello();  
  7.     }  
  8. }  

至此,所有的警告都消失了。

编译器警告的限制

在我们实际编码过程中,引用类型可以在方法,类,甚至程序集中传递。因此抛出警告时,它并不是万无一失的。例如,我们有如下代码:

  1. class MyClass  
  2. {  
  3.     public Random Random = new Random();  
  4. }  
  5.    
  6.    
  7. static void Main(string[] args)  
  8. {  
  9.     MyClass myClass = new MyClass();  
  10.     SomeMethod(myClass);  
  11.     var next = myClass.Random.Next(1, 10);  
  12. }  
  13.    
  14. static void SomeMethod(MyClass myClass)  
  15. {  
  16.     myClass.Random = null;  
  17. }  

这里编译器只会警告我们在分配一个null值给一个没有明确指定可空的变量。但是我们不会得到Possible dereference的警告。这里我们可以推断,一旦将对象传递到方法之外,无论在那里发生什么(如设置null),我们都不会被警告。但是如果我们在相同的代码/方法块中如此明确地分配null,然后尝试使用它,那么编译器将尝试给我们一个帮助。

为了与上述代码比较,以下代码确实会收到2条警告

  1. static void Main(string[] args)  
  2. {  
  3.     MyClass myClass = new MyClass();  
  4.     if (new Random().Next(1, 10) > 5)  
  5.     {  
  6.         myClass = null;  
  7.     }  
  8.    
  9.     myClass.SayHello();  
  10. }  

启用可空引用类型的严格模式

如果你希望用错误替换警告,你可以升级整个检查到严格模式。这里你只需要在项目的csproj文件中添加一行:

  1. <TreatWarningsAsErrors>true</TreatWarningsAsErrors>  

注意: 这会将所有警告视为错误,而不仅仅是关于空引用问题的警告。但这意味着如果有警告被抛出,你的项目将不再编译!

钰玺

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: