天天看點

為你的項目啟用可空引用類型

使用 C# 中的可空引用類型

為你的項目啟用可空引用類型

Intro

C# 從 8.0 開始引入了可空引用類型,我們可以為項目啟用可空引用類型來借助編譯器來幫助我們更好的處理代碼中的空引用的處理,可以避免我們寫很多不必要 null 檢查,提高我們的效率

Why

為什麼我們要啟用可空引用類型呢,首先我們可以看一下 asp.net core 項目,asp.net core 的項目正在大量的使用可空引用類型,詳情可以參考:

https://github.com/dotnet/aspnetcore/issues/5680

Updating ASP.NET Core to use C# 8's nullable reference types would:
  1. Help ASP.NET Core libraries avoid null reference exceptions internally. It will help us find and prevent our bugs and increase our developer productivity
  2. Provide guidance to developers who are using ASP.NET Core about which APIs can accept and return a null reference and which APIs can't. This would improve the developer experience of using ASP.NET Core

主要分為兩方面,一方面是内部的代碼,對于内部代碼而言,使用可空引用類型我們可以借助編譯器清晰地了解一個變量是否會為

null

,不會為

null

的變量就不再需要進行空檢查了,另一方面是對于使用的代碼,對于使用啟用空引用類型的類庫,編譯器可以提供更好的空檢查支援,開發者可以清晰地了解哪些 API 是允許為

null

,哪些 API 是不允許為

null

的,對開發者更為友好

How

接着我們就來看一看如何為我們的項目啟用可空引用類型吧,微軟的文檔上提供了比較詳細的說明,詳細可以參考文末的引用連結

啟用可空引用類型隻需要在項目檔案中添加

<Nullable>enable</Nullable>

即可,

LangVersion

需要設定為

8

及以上。

Nullable

上下文包含了兩個上下文一個是

Nullable annotation context

(支援

?

表示可為空的引用類型),一個是

Nullable warning context

(支援編譯器針對可空引用類型的警告)

Nullable

上下文有 4 種配置,配置如下

  • enable

  • warnings

  • annotations

  • disable

Setting Warning Context Status Annotation Context Status
enable enabled
warning disabled
annotations
disable

推薦直接使用

enable

啟用可空引用類型,隻啟用

annotation

上下文,編譯器不會針對可空引用類型的檢查做出警告,意義就不太大了,隻啟用

warning

上下文,可以使用在不想在自己應用中啟用可空引用類型,可以嘗試這個配置,不配置

nullable

或者配置

disable

則可以完全禁用可空引用類型

除了針對 project 的 global 的配置之外,我們還可以在項目源代碼裡通過

#nullable

來改變局部的可空上下文配置,通過

#nullable restore

恢複預設的可空引用上下文配置

  • #nullable enable

    : 設定 nullable annotation context 和 nullable warning context 為 enabled.
  • #nullable disable

    : 設定 nullable annotation context 和 nullable warning context 為 disabled.
  • #nullable restore

    : 恢複 nullable annotation context 和 nullable warning context 為項目預設的配置.
  • #nullable disable warnings

    : 設定 nullable warning context 為 disabled.
  • #nullable enable warnings

    : 設定 nullable warning context 為 enabled.
  • #nullable restore warnings

    : 恢複 nullable warning context 為項目配置
  • #nullable disable annotations

    : 設定 nullable annotation context 為 disabled.
  • #nullable enable annotations

    : 設定 nullable annotation context 為 enabled.
  • #nullable restore annotations

    : 恢複 annotation warning context 為項目配置

啟用可空引用類型之後,引用類型就不允許被設定為

null

,如果要設定為

null

,可以在類型後加一個

?

設定為可空的引用類型如

string?

,或者使用

!

讓編譯器允許指派,如:

string a = null!;

(這也是我們需要注意的一個地方,可空引用類型隻是編譯器的檢查,并不能夠嚴格的保證不會被指派為

null

,對于類庫項目,如果

public

的 API 期望的參數是不可空的引用類型,除了使用不可空引用類型外,還是需要保留

null

檢查)

Sample

首先可以看一個接口:

public interface IPropertyConfiguration<out TEntity, TProperty>
{
    IPropertyConfiguration<TEntity, TProperty> HasColumnTitle(string title);

    IPropertyConfiguration<TEntity, TProperty> HasColumnFormatter(string? formatter);

    IPropertyConfiguration<TEntity, TProperty> HasColumnInputFormatter(Func<string?, TProperty?>? formatterFunc);
}
           

來看實作:

internal sealed class PropertyConfiguration<TEntity, TProperty> : PropertyConfiguration, IPropertyConfiguration<TEntity, TProperty>
{
    private readonly PropertyInfo _propertyInfo;

    public PropertyConfiguration(PropertyInfo propertyInfo)
    {
        _propertyInfo = propertyInfo;
        PropertyName = propertyInfo.Name;
        ColumnTitle = propertyInfo.Name;
    }

    public IPropertyConfiguration<TEntity, TProperty> HasColumnTitle(string title)
    {
        ColumnTitle = title ?? throw new ArgumentNullException(nameof(title));
        return this;
    }

    public IPropertyConfiguration<TEntity, TProperty> HasColumnFormatter(string? formatter)
    {
        ColumnFormatter = formatter;
        return this;
    }

    public IPropertyConfiguration<TEntity, TProperty> HasInputFormatter(
        Func<TEntity?, TProperty?, TProperty?>? formatterFunc)
    {
        InternalCache.InputFormatterFuncCache.AddOrUpdate(_propertyInfo, formatterFunc);
        return this;
    }
}
           

可以看到

HasColumnTitle

的參數中的

title

是不可空的引用類型,即使如此實作代碼裡還是做了

null

檢查,而且可空引用類型在

throw new ArgumentNullException()

的時候也不會引發警告

警告示例:

如果指派

null

給一個不可為空的引用類型時,編譯器就會給出一個警告,示例如下:

為你的項目啟用可空引用類型

在往一個不可空引用類型清單裡中添加

null

時,編譯器也會給出一個警告:

為你的項目啟用可空引用類型

如果一個可空的的引用類型變量沒有檢查

null

的時候,也會有警告:

為你的項目啟用可空引用類型

從上圖中可以看出,使用

var

聲明變量的時候,會是一個可空的引用類型

More

使用可空引用類型可以一定程度上幫助我們減少不必要的 null 檢查,但是對于類庫項目來說,該有的 null 檢查還是要有的

對于應用來說,借助可空引用類型也可以比較清晰地了解,哪些地方需要檢查 null,哪些地方不需要,可以提升代碼品質

對于

null

包容運算符

!

,可以将一個可能

null

的對象指派給不可空的引用類型變量,盡量不用使用,用了這個就是自己在代碼裡埋雷,本來不會為

null

的變量、屬性也會出現

null

的情況,如果還沒有必要的

null

檢查,完全是自己給自己挖坑。

但是在使用過程中,感覺有些情況下還是不夠智能,在測試項目中

Assert

的時候就不能很好的工作,來看一個示例:

為你的項目啟用可空引用類型

從上面的示例來看,在使用

importedList[i].Id/Title

之前已經使用了

Assert.NotNull(importedList[i])

,理論上來說

importedList[i]

是不會為

null

的,但是編譯器現在還沒這麼智能,還需要進一步的優化,針對這樣的情況,可以單獨聲明一個變量,使用

!

來聲明一個不可空的引用類型,想要禁用測試項目中的警告的話也可以設定

nullable

級别為

annotations

或者

disabled

最後想說,鑒于目前 asp.net core 正在大力采用可空引用類型,大家還是可以了解一下的

Reference

  • https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
  • https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/upgrade-to-nullable-references
  • https://docs.microsoft.com/en-us/dotnet/csharp/nullable-migration-strategies
  • https://github.com/WeihanLi/WeihanLi.Npoi/pull/98
  • https://github.com/WeihanLi/DbTool
  • https://github.com/dotnet/samples/tree/master/csharp/NullableIntroduction/NullableIntroduction
  • https://stackoverflow.com/questions/54526652/when-to-null-check-arguments-with-nullable-reference-types-enabled
  • https://headspring.com/2020/06/02/applying-nullable-reference-types-in-fixie/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。