CodeGym /課程 /C# SELF /編譯器警告分析

編譯器警告分析

C# SELF
等級 14, 課堂 4
開放

1. 前言

想像一下,程式碼就像一齣舞台劇,變數就是演員。每次變數進入新場景(code block),它可能是「活著的」(有值)或「幽靈」(null)。但問題來了:觀眾——也就是編譯器——不是現場看戲,而是看劇本。他只看到演員什麼時候被分配角色(賦值),什麼時候消失(變成 null)。但他沒辦法看到現實(程式執行時),只能根據劇本猜!

資料流靜態分析,就是編譯器在程式還沒跑之前,先檢查你的 code,看看會不會有人演出「NullReferenceException 大崩潰」的戲碼。

這個流程的重點

從 C# 8.0 開始,有了 Nullable Reference Types(NRT)模式,編譯器會分析變數在某些執行路徑下會不會變成 null。如果有可能,他馬上就會提醒你。他不會讓你「用一個突然變成幽靈的演員的名字」。

2. 怎麼看懂編譯器警告

主要的警告類型

開啟 NRT 模式後,編譯器會特別盯著 reference 型別的變數(像 string、User、陣列、物件)。最常見的警告有這幾種:

  • Possible null assignment — 你想把可能是 null 的值賦給一個不能是 null 的變數。
  • Dereference of a possibly null reference — 你要用物件的成員,但編譯器懷疑它可能是 null。
  • Possible null return — 方法可能回傳 null,但簽名規定一定要有值。
  • Nullable object must have a value — 你在沒檢查的情況下用 .Value 取 nullable 型別的值。
  • Unassigned non-nullable property — 你沒給一個不能是 null 的欄位/屬性賦值。

通常在 IDE(像 Rider 或 Visual Studio)裡,這些警告會被波浪線標記,滑鼠移過去會有說明。

典型警告範例


#nullable enable

string? possibleNull = GetString();

// 哎呀,編譯器在抱怨:"Dereference of a possibly null reference"
int length = possibleNull.Length; // 警告!

編譯器不確定 possibleNull 會不會是 null,所以你用 .Length 可能會爆掉。

如果方法長這樣:

string? GetString() { ... }

然後你寫:

string nonNullable = GetString();

你就會收到警告:可能把 null 傳給 non-nullable 變數,這樣不安全。

3. 編譯器怎麼分析你的 code

範例 1:直接賦值


string? name = null;
Console.WriteLine(name.Length); // 警告:可能存取到 null

這很明顯:name 是 null,你要用它的屬性就很危險。

範例 2:檢查 null


string? name = GetUserName();

if (name != null)
{
    Console.WriteLine(name.Length); // 沒問題!
}
else
{
    Console.WriteLine("名字沒填!");
}

編譯器知道在 if (name != null) 裡面,變數是安全的。這就是 flow analysis——根據條件分析值的流向。

4. 編譯器沒辦法「看進去方法裡面」

他只看簽名——如果寫說方法回傳 string?,他就相信可能會是 null,就算裡面永遠回傳字串也一樣。

範例 3:從方法回傳值


string? GetMaybeName(bool useName)
{
    if (useName)
        return "Code Jedi";
    else
        return null;
}

void PrintLength()
{
    string? name = GetMaybeName(false);

    Console.WriteLine(name.Length); // 警告!
}

編譯器看到 GetMaybeName 可能 回傳 null,所以要你小心一點。

5. 常見情境與警告

把 nullable 賦給 non-nullable 變數


string? maybeUser = GetUser();
string alwaysUser = maybeUser; // warning: possible null assignment

要消除警告可以這樣:


string alwaysUser = maybeUser ?? "訪客";

這樣 always 會有字串——要嘛來自方法,要嘛就是「訪客」。

忘了檢查 .HasValue 的 nullable value 型別


int? age = GetAge();
int realAge = age.Value; // warning: 可能存取到 null

你應該這樣寫:


if (age.HasValue)
    Console.WriteLine(age.Value);

或是這樣:


int realAge = age ?? -1;

自動屬性沒初始化


class User
{
    public string Name { get; set; } // warning: 沒有初始化!
}

解法:


public string Name { get; set; } = "沒名字";

或


public string? Name { get; set; }

6. 複雜一點的 flow 分析

在迴圈裡分析


string? text = null;

while (text == null)
{
    text = Console.ReadLine();
}

Console.WriteLine(text.Length); // ok:編譯器懂了!

不同路徑的賦值


string? name;
if (Random.Shared.Next() % 2 == 0)
    name = "Alice";
else
    name = null;

Console.WriteLine(name.Length); // warning: possible null dereference

編譯器會分析所有分支。只要有一條路是 null,他就會警告你。

7. 怎麼「安撫」編譯器(還有什麼時候不該這樣做)

有時你很確定值不會是 null,但編譯器不信你:


string? value = GetSomething();
if (value == null)
    throw new Exception("預期要有值!");

Console.WriteLine(value.Length); // 警告消失了

或者你用 ! 運算子:


string? value = GetSomething();
Console.WriteLine(value!.Length); // 沒警告,但有風險

這叫做 null-forgiving operator。他什麼都不檢查——只是跟編譯器說:「放心啦,我有把握」。但如果你搞錯,執行時還是會爆掉。

2
任務
C# SELF, 等級 14, 課堂 4
上鎖
在使用變數之前檢查是否為 null
在使用變數之前檢查是否為 null
2
任務
C# SELF, 等級 14, 課堂 4
上鎖
用 null 合併運算子消除警告
用 null 合併運算子消除警告
1
問卷/小測驗
Nullable-型別,等級 14,課堂 4
未開放
Nullable-型別
Nullable Reference Types 入門
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION