C# 8.0 搶先看 -- nullable reference types

None null 的參考型別來了,這是怎麼一回事呢?

本篇文章使用環境
開發環境 Visual Studio 2019 Preview 3 (16.0.0 Preview 3)
框架       .NET Core 3.0.100-preview3-010272
編譯器    C# 8.0 beta

這個新特性的名稱是 『Nullable reference types』,還挺有趣的,reference type 本來就可以 null 的不是,這個新的設定方式會讓編譯器檢查『必須將非 null 值指派給未設定為 nullable 的參考型別變數』,設定方式很簡單,分為 (1) 前置處理指示詞設定 (2) 專案設定檔內設定 兩種方式。

前置處理指示詞設定

 這個設定方式很簡單,在單一的 cs code file 上使用 #nullable enable 宣告即可,例如下方的程式碼:

using System;
#nullable enable
namespace NullableReferenceSample001
{
    /// <summary>
    /// 使用 #nullable enable 宣告
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            string s1 = "ABC";
            string? s2 = null;
            string s3 = null;
            Console.WriteLine(s1);
            Console.WriteLine(s2);
            Console.WriteLine(s3);
        }
    }
}

在上述的程式碼,我們看到了三個 string 型別變數的宣告,s1 宣告的同時給予了非 null 的初始值,所以沒有問題。

s2 的宣告上使用了 string? 宣告,這個宣告表示 s2 變數是一個 nullable 的 string 型別變數 (很類似 Nullable<T> 的宣告方式,但意義上不太一樣),參考型別? 這樣的宣告僅適用於已經宣告了 nullable reference types 的程式碼檔案或專案。

在上述範例的 s3 就會出問題了,因為 s3 變數對應的 string 型別並未宣告為 nullable ,因此指派 null 給此變數會使得 Visual Studio 對此發出警告,這不會造成編譯錯誤,只有警告而已,如圖:

剛剛是區域變數的情況,下一個範例來看看在欄位或自動實作屬性的情況,我們以自動實作屬性為例:

using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace NullableReferenceSample001
{
    /// <summary>
    /// 使用 ? 宣告 MiddleName 為 nullable
    /// None-null 的欄位必須要在建構式設定初始值,且不能傳入 null
    /// </summary>
    public class Person
    {
        public string FirstName { get; set; }
        public string? MiddleName { get; set; }
        public string LastName { get; set; }

        public Person(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }
    }
}

在上述的程式碼中, FirstName 和 LastName 被宣告為 none-nullable,如果沒有在建構式給予這兩個屬性非 null 的初始值,編譯器據此會警告程式開發人員應該給予這兩個屬性非 null 初始值 (在 preview 3  的時候,這個警告的內容有點奇妙,我猜想正式版應該會改好一點吧。)

 

專案檔內設定

 

若是我們在專案檔內設定的話,基本上就是表示整個專案都傾向於檢查 nullable reference 這件事,設定的方式很簡單,打開 .csproj (在 VS 2019 這件事情異常的容易,只要在方案總管連點兩下專案名稱就行了),接著加入<NullableContextOptions>enable</NullableContextOptions> 就可以了。如下所示:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

註:在 preview 1 的時候,這個設定本來是 <NullableReferenceTypes>true</NullableReferenceTypes> ,但 preview 2 後變更為  <NullableContextOptions>enable</NullableContextOptions> 。16.2 preview1 後又變更為  <Nullable>enable</Nullable>。

在這個情況下,前置指示詞可以設定 #nullable disable 表示該檔案不啟用 nullable reference types 的功能,例如:

using System;
using System.Collections.Generic;
using System.Text;
#nullable disable 
namespace NullableReferenceSample002
{
    /// <summary>
    /// 使用 nullable disable 取消此檔案的設定
    /// </summary>
    public class Person
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
       
    }
}

這功能到目前讓我覺得有個很詭異的地方 (純粹個人意見),我們假設有個已經設定好支援 nullable reference types 功能的類別庫專案,取名叫 NullableLibrary,其中有個 Person Class 如下:
 

    public class Person
    {
        public string FirstName { get; set; }
        public string? MiddleName { get; set; }
        public string LastName { get; set; }

        public Person(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }
    }

接著我們建立另外一個 Console 的應用程式,引用此 NullableLibrary 為參考,但沒有設定啟用 nullable reference types 功能,這時在這個 Console 中使用 Person 建構式傳入 null 為參數的時候並不會產生警告,如下圖:

也就是說除非這個 Program class 所處的檔案也有標註為支援 nullable reference types,否則這警告是不會出現的,接著補上宣告,就會出現編譯器警告,如圖:

這個結果顯示要嘛就是全部人都要啟用 nullable reference types ,不然還是會遺漏提醒的警告,不過現在還是 preview 3,或許下一版就會有改變也不一定。