解決可為 Null 的警告
可空性警告的目的,是盡可能降低應用程式執行時拋出 System.NullReferenceException 的機會。 為了達成此目標,編譯程式會在您的程式代碼具有可能導致 Null 參考例外狀況的建構時,使用靜態分析和發出警告。 您可套用型別註釋和屬性,為編譯器提供靜態分析的資訊。 這些註釋和屬性描述型別引數、參數和成員的可 Null 性。 在本文中,您將瞭解不同的技術來解決由編譯器的靜態分析產生的可為 Null 的警告。 此處所述的技術適用於一般 C# 程式碼。 請參閱使用可為 Null 的參考型別,了解如何使用可為 Null 的參考型別和 Entity Framework 核心。
只有在可空性上下文設定為 enable
或 annotations
時,才允許使用可為 Null 的參考型別,包括運算子 ?
和 !
。 您可以在專案檔案中使用編譯器選項 Nullable
,或者在原始碼中使用 #nullable
pragma,以設定允許 Null 的上下文。
本文涵蓋下列編譯器警告:
- CS8597 - 擲回值可能為 Null。
- CS8598 - 此環境中禁止使用抑制運算子
- CS8600 - 正在將 null 文字常值或可能的 null 值轉換為不可為 null 的型別。
- CS8601 - 可能存在 Null 參考指派。
- CS8602 - 可能為 Null 之引用的解參考。
- CS8603 - 可能會有空參考傳回。
- CS8604 - 可能的空引用作為參數。
- CS8605 - 正在解封可能為空的值。
-
CS8607 - 可能的 Null 值不能用於標示
[NotNull]
或[DisallowNull]
的型別 - CS8608 - 參考型別的可空性與型別中被覆寫成員的不一致。
- CS8609 - 傳回型別的參考型別可空性與覆寫的成員不一致。
- CS8610 - 型別參數中參考型別的空值性與覆寫的成員不一致。
- CS8611 - 類型參數中的參考型別的 Null 性與部分方法的宣告不符。
- CS8612 - 型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8613 - 傳回類型中的參考類型可空性與隱式實作的成員不匹配。
- CS8614 - 參數型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8615 - 類型中參考型別的可空性與實作的成員不匹配。
- CS8616 - 傳回型別中參考型別的可 Null 性與實作的成員不符合。
- CS8617 - 參數類型中參考類型的 null 性與所實現成員的不匹配。
- CS8618 - 非空變數在結束建構函式時必須包含非空值。請考慮將其宣告為可空。
- CS8619 - 值中參考型別的可 Null 性與目標型別不符合。
- CS8620 - 由於參考型別的可空性不同,因此引數無法用於參數。
- CS8621 - 傳回型別中參考型別的可 Null 性與目標委派不相符 (可能的原因是可 Null 性屬性)。
- CS8622 - 參數類型中參考型別的可空性與目標委派不相符 (可能的原因是可空性屬性)。
- 禁止明確地應用
System.Runtime.CompilerServices.NullableAttribute
。CS8623 - - CS8624 - 因為參考型別的可 Null 性有所差異,所以引數無法作為輸出。
- CS8625 - 無法將 null 字面值轉換成不可為 null 的參考類型。
- CS8628 - 無法在物件建立中使用可為 Null 的參考型別。
- CS8629 - 可為 Null 的值型別可能為 Null。
- CS8631 - 此類型無法作為型別參數用於泛型類型或方法。型別引數的可空性與約束類型不符。
-
CS8632 - 可為 Null 參考型別的註釋應該只用於
#nullable
批註內容內的程式碼中。 - CS8633 - 方法的型別參數之約束可空性不符合介面方法的型別參數之約束。請考慮改用明確的介面實作。
- CS8634 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「類別」條件約束。
-
CS8636 -
/nullable
無效的選項;必須是disable
、enable
、warnings
或annotations
-
CS8637 - 預期
enable
、disable
或restore
- CS8639 - typeof 運算符不能用於可為 Null 的參考類型
- CS8643 - 介面實作類型中的參考類型的可 Null 性與明確介面指定符不相符。
- CS8644 - 類型未實作介面成員。基底類型所實作之介面中的參考類型的可 NULL 性不相符。
- CS8645 - 成員已經以不同的可為 Null 的參考型別列在類型的介面清單中。
- CS8655 - switch 表達式無法處理某些 Null 輸入(並非完整)。
- CS8667 - 部分方法的宣告在類型參數的限制中,其可空性表現不一致。
- CS8670 - 物件或集合初始化器隱式地解除可能為 Null 的成員的參考。
- CS8714 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「非 NULL」條件約束。
- CS8762 - 參數在結束時必須有非空值。
-
CS8763 - 標示
[DoesNotReturn]
的方法不應傳回。 - CS8764 - 傳回型別的 Null 性與覆寫的成員不相符(可能是因為可空性屬性)。
- 參數類型的可空性與覆寫的成員不相符 (可能是因為可空性屬性導致的)。
- CS8766 - 傳回型別中的參考型別可空性與隱含實作成員的可空性不相符 (可能是由於可空性屬性所致)。
- CS8767 - 參數類型中參考型別是否可為 Null 的情況,與隱含實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8768 - 傳回類型中參考型別的可空性與實作的成員不相符 (可能是由於可空性屬性)。
- CS8769 - 參數類型中的參考型別是否可為 Null 與已實作的成員不符(可能因為 Null 性屬性)。
-
CS8770 - 方法缺少
[DoesNotReturn]
註釋,與實作或覆寫的成員不相符。 - CS8774 - 成員在結束時必須具有非空值。
- CS8776 - 成員不可用於此屬性中。
- CS8775 - 成員在結束時必須有非 null 值。
- CS8777 - 參數在結束時必須有非空值。
- CS8819 - 回傳型別的參考型別可空性與部分方法定義不一致。
- CS8824 - 參數在結束時必須有非 Null 值,因為參數為非 Null。
- CS8825 - 因為參數不是 Null,所以傳回值必須非 Null。
- CS8847 - switch 表達式不處理某些空值輸入 (並不完整)。不過,具有 "when" 子句的模式可能會成功匹配此值。
注意
靜態分析不一定能夠在特定情境下判斷方法的存取順序,以及該方法是否在不擲回例外的情況下順利完成。 這些已知的陷阱在 已知陷阱 一節中描述。
您使用以下五種技術之一來處理幾乎所有的警告:
- 配置可空的上下文。
- 新增必要的空值檢查。
- 新增或移除
?
或!
的可空註釋。 - 新增描述 Null 語意的屬性。
- 正確初始化變數。
如果您不熟悉使用可為 Null 的參考型別,可為 Null 參考型別的概觀 提供可為 Null 參考型別可解決的背景,以及它們如何為您的程式代碼中可能發生的錯誤提供警告。 您也可以檢查 移轉至可為 Null 參考型別的指引, 深入瞭解如何在現有專案中啟用可為 Null 的參考型別。
設定可空的上下文環境
下列警告表示您尚未正確設定可為 Null 的上下文:
-
CS8632 - 可空性參考型別的註釋只應用於
#nullable
批註內容內的程式碼中。 -
CS8636 -
/nullable
無效的選項;必須是disable
、enable
、warnings
或annotations
-
CS8637 - 預期
enable
、disable
或restore
不正確的註釋語法
這些錯誤和警告表示 !
或 ?
批注的使用方式不正確。
- CS8598 - 在此內容中不允許隱藏運算子
- 不允許 CS8623 - 明確應用
System.Runtime.CompilerServices.NullableAttribute
。 - CS8628 - 無法在物件建立中使用可為 Null 的參考類型。
- CS8639 - typeof 運算符不能用於可為 Null 的參考類型
宣告中的 ?
批注表示變數可能是 null。 它不會指出不同的運行時間類型。 下列兩個宣告都是相同的運行時間類型:
string s1 = "a string";
string? s2 = "another string";
?
是編譯程式對空值預期的提示。
表達式上的 !
批注表示您知道表達式是安全的,而且應該假設不是 null。
- 您必須使用這些批注,而不是程序代碼中的 System.Runtime.CompilerServices.NullableAttribute。
- 因為
?
是批注,而不是類型,所以您無法將它與typeof
或new
運算式搭配使用。 -
!
運算子無法套用至變數表達式或方法群組。 -
!
運算子不能套用到成員存取運算子的左側,例如obj.Field!.Method()
。
Null 的可能取值
這些警告會提醒您正在對一個其Null 狀態為可能為 Null的變數進行解引用。 這些警告如下:
- CS8602 - 解引用可能為 null 的參考。
- CS8670 - 物件或集合初始設定式可能隱含解參考 null 成員。
下列程式碼示範上述警告的各個範例:
class Container
{
public List<string>? States { get; set; }
}
internal void PossibleDereferenceNullExamples(string? message)
{
Console.WriteLine(message.Length); // CS8602
var c = new Container { States = { "Red", "Yellow", "Green" } }; // CS8670
}
在上述範例中,警告是因為 Container
、 c
可能有 States
屬性的 null 值。 將新狀態指派給可能為 Null 的集合時,便會導致警告。
若要移除這些警告,您必須新增程式碼,以便在取值之前將該變數的 空狀態變更為非空。 集合初始化器警告可能較難發現。 在初始設定式將元素加入集合時,編譯器會偵測該集合可能為 Null。
在許多情況下,您可以在解引用前先檢查變數不為 null,以解除這些警告。 請考慮以下範例,在解參考 message
參數之前,加入空值檢查:
void WriteMessageLength(string? message)
{
if (message is not null)
{
Console.WriteLine(message.Length);
}
}
下列範例會初始化 States
的備份儲存體,並移除 set
存取子。 類別的取用者可以修改集合的內容,而且集合的儲存體絕不是 null
:
class Container
{
public List<string> States { get; } = new();
}
當您收到這些警告時,這些情況可能是誤報。 您可能有可測試 Null 的私人公用程式方法。 編譯器不知道這項方法提供空值檢查。 請考慮使用私人公用程式方法 IsNotNull
的下列範例:
public void WriteMessage(string? message)
{
if (IsNotNull(message))
Console.WriteLine(message.Length);
}
編譯程式會警告您當您寫入屬性 message.Length
時,可能會解參照到 null,因為其靜態分析判斷 message
可能為 null
。 您知道 IsNotNull
提供 null 檢查,當傳回 true
時,message
的 null 狀態 應該 非空。 你必須告知編譯器這些事實。 有一種方法是使用 Null 允許運算符 !
。 您可變更 WriteLine
陳述式,以符合下列程式碼:
Console.WriteLine(message!.Length);
Null 容許運算子會讓運算式為非 Null,即使運算式可能為 Null,且不套用 !
。 在此範例中,更好的解決方案是將屬性新增至 IsNotNull
的特徵標記:
private static bool IsNotNull([NotNullWhen(true)] object? obj) => obj != null;
System.Diagnostics.CodeAnalysis.NotNullWhenAttribute 會通知編譯器,當方法傳回 obj
時,用於 參數的引數true
。 當方法傳回 false
時,引數的 Null 狀態與呼叫方法前的狀態相同。
提示
您可使用一系列豐富屬性,以描述方法和屬性對 Null 狀態的影響。 如需深入了解,您可參閱語言參考文章中的可為 Null 的靜態分析屬性。
要修正可能為 Null 變數取值的警告,可使用以下三種技術之一:
- 新增遺漏的 Null 檢查。
- 在 API 上新增 Null 分析屬性,以影響編譯器的 Null 狀態靜態分析。 呼叫方法後,這些屬性會通知編譯器,傳回值或引數應可能為 Null 或非 Null。
- 將 null 瀏覽運算子
!
套用至表達式,使其狀態 必定為非 null。
已將可能的空值指派給不可為空值的參考
這些警告提醒您,您正在將一個類型不可為 Null 的變數指派給一個 Null 狀態為可能為 Null 的運算式。 這些警告如下:
- CS8597 - 擲回值可能為 Null。
- CS8600 - 正在將 Null 常值或可能的 Null 值轉換為不可為 Null 的型別。
- CS8601 - 可能有空參考分配。
- CS8603 - 可能有空值參考傳回。
- CS8604 - 參數可能有 Null 參考引數。
- CS8605 - 解開一個可能為 Null 的值。
- CS8625 - 無法將 null 常值轉換成不可為 null 的參考型別。
- CS8629 - 可為 Null 的值類型可能為 Null。
當您嘗試將可能為 Null 的運算式指派給不可為 Null 的變數時,編譯器會發出這些警告。 例如:
string? TryGetMessage(int id) => "";
string msg = TryGetMessage(42); // Possible null assignment.
不同的警告會提供程式碼的詳細資訊,例如指派、unboxing 指派、傳回語句、方法的參數,以及拋出運算式。
您可採取三個動作之一來解決這些警告。 其中一個是新增 ?
註釋,讓變數成為可為 Null 的參考型別。 該變更可能會導致其他警告。 將變數從不可為 Null 的參考變更為可為 Null 的參考,其預設 Null 狀態會從非 Null 變更為可能為 Null。 編譯程式的靜態分析會尋找您取值變數的實例,該變數 可能為 null。
其他動作會向編譯器指示,賦值運算式的右側為非空值。 右側的表達式可以在指派前檢查是否為 Null,如以下範例所示:
string notNullMsg = TryGetMessage(42) ?? "Unknown message id: 42";
之前的範例展示了方法傳回值的賦值。 您可以標註 方法 (或 屬性) 以指出方法何時傳回非 Null 值。 輸入引數System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute 時, 通常會將傳回值指定為非 Null。 另一個替代方法是將 Null 容許運算子 !
新增至右側:
string msg = TryGetMessage(42)!;
修正可能為 Null 運算式指派給非 Null 變數的警告,涉及以下四種方法之一:
- 將指派的左側變更為可為 Null 的類型。 當您取值該變數時,此動作可能會引入新的警告。
- 在指派前進行空值檢查。
- 請標記產生賦值右側的 API。
- 將 Null 容許運算子新增至指派的右側。
不可為 Null 的引用未初始化
此警告組合會警示,您正在將不可為 Null 的變數類型指派給 Null 狀態可能為 Null 的運算式。 這些警告如下:
- CS8618 - 不為 Null 的變數在離開建構函式時必須有一個不為 Null 的值。請考慮將其宣告為允許 Null。
- CS8762 - 參數在結束時必須有非空值。
請考慮下列類別作為範例:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
FirstName
和 LastName
皆不保證初始化。 若此為新程式碼,請考慮變更公用介面。 上述範例可以更新如下:
public class Person
{
public Person(string first, string last)
{
FirstName = first;
LastName = last;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
若需要在設定名稱前建立 Person
物件,您可使用預設的非 Null 值來初始化屬性:
public class Person
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
}
另一個替代方法是將這些成員變更為可空的參考型別。 若名稱應允許 Person
,則可定義 null
類別如下:
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
現有的程式代碼有時需要其他變更,以通知編譯程式這些成員的 Null 語意。 它可能有多個建構函式,而您的類別具有可初始化一或多個成員的私人協助程式方法。 您可將初始化程式碼移至單一建構函式,並確保所有建構函式皆使用共同的初始化程式碼來呼叫建構函式。 或者,您也可使用 System.Diagnostics.CodeAnalysis.MemberNotNullAttribute 和 System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute 屬性。 這些屬性會通知編譯程式,在方法傳回之後,成員 非 Null。 下列程式碼將示範各項作業。
Person
類別使用其他所有建構函式呼叫的通用建構函式。
Student
類別有一個具有 System.Diagnostics.CodeAnalysis.MemberNotNullAttribute 屬性標註的輔助方法:
using System.Diagnostics.CodeAnalysis;
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public Person() : this("John", "Doe") { }
}
public class Student : Person
{
public string Major { get; set; }
public Student(string firstName, string lastName, string major)
: base(firstName, lastName)
{
SetMajor(major);
}
public Student(string firstName, string lastName) :
base(firstName, lastName)
{
SetMajor();
}
public Student()
{
SetMajor();
}
[MemberNotNull(nameof(Major))]
private void SetMajor(string? major = default)
{
Major = major ?? "Undeclared";
}
}
最後,您可以使用 null 允許運算子來表明成員是在其他程式碼中初始化的。 如需其他範例,請考慮代表 Entity Framework Core 模型的下列類別:
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; } = null!;
}
DbSet
屬性會初始化為 null!
。 這可告知編譯器屬性已設為not-null 值。 事實上,基底 DbContext
會執行該集合的初始化。 編譯器的靜態分析功能無法檢測到這一點。 如需關於可為 Null 的參考類型和 Entity Framework Core 的詳細資訊,請參閱EF Core 中可為 Null 的參考類型文章。
修正不允許 Null 成員未初始化的警告,可以使用以下四種方法之一:
- 變更建構函式或欄位初始設定式,以確保所有不可為 Null 的成員皆已初始化。
- 將一或多個成員變更為可為 Null 的類型。
- 標註任何輔助方法,來指出哪些成員被指派。
- 將初始設定式新增至
null!
,表示該成員在其他程式碼中初始化。
Null性宣告不符合
許多警告指出方法、委派或型別參數的簽名中存在可空性不匹配。
- CS8608 - 類型中的引用類型的可空性與被覆寫的成員不匹配。
- CS8609 - 傳回型別中參考型別的可空性與覆寫的成員不符合。
- CS8610 - 型別參數中參考型別的 Null 特性與覆寫成員不相符。
- CS8611 - 型別參數中的參考型別可空性與部分類方法宣告不符。
- CS8612 - 型別中的參考型別可空性與隱含實作的成員不一致。
- CS8613 - 傳回型別中的參考型別的可空性與隱式實作的成員不一致。
- CS8614 - 參數型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8615 - 類型中的參考類型的可空性與實作成員不一致。
- CS8616 - 傳回型別中的參考類型可空性與實作的成員不符。
- CS8617 - 參數型別中參考型別的可 Null 性與實作的成員不符合。
- CS8619 - 值中參考型別的可 Null 性與目標型別不符合。
- CS8620 - 因為參考型別的可 Null 性有所差異,所以引數無法用於參數。
- CS8621 - 返回類型中引用類型的可空性與目標委託不匹配(可能是因為可空性屬性)。
- CS8622 - 參數中參考型別的可空性與目標委派不匹配 (可能是由於可空性屬性所致)。
- CS8624 - 因為參考型別的可 Null 性有所差異,所以引數無法作為輸出。
- CS8631 - 類型在泛型型別或方法中不能作為類型參數使用。型別參數的可空性不符合約束類型。
- CS8633 - 方法型別參數的可空性約束與介面方法型別參數的約束不匹配。請考慮使用明確的介面實作。
- CS8634 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「類別」條件約束。
- CS8643 - 明確介面規範中的參考類型的可 Null 性與該類型所實作的介面不相符。
- CS8644 - 類型未實作介面成員。基底類型所實作的介面中,參考類型的可空性不相符。
- CS8645 - 成員已列在類別的介面清單中,且具有不同的參考型別的 Null 性。
- CS8667 - 部分方法宣告在類型參數的限制式中,有不一致的可空性。
- CS8714 - 此類型在泛型類型或方法中不能作為類型參數使用。類型引數的可空性不符合 'notnull' 約束。
- CS8764 - 傳回型別的 Null 值屬性與覆寫成員不匹配 (可能是因為 Null 值屬性設定的影響)。
- CS8765 - 參數的可空性與覆寫成員的不相符(可能是因為可空性屬性)。
- CS8766 - 傳回型別中參考型別的可為 Null 性質與隱式實作的成員不相符 (可能原因是可為 Null 屬性)。
- CS8767 - 參考型別之空性與參數類型中的隱式實作成員不匹配(可能是因為空性屬性)。
- CS8768 - 傳回類型中的參考型別的可空性與已實作成員不匹配(可能是因為可空性屬性)。
- CS8769 - 參數類型中參考型別的可空性與實作成員不匹配(可能是因為可空性屬性導致)。
- CS8819 - 回傳型別中的參考型別的可空性與部分方法宣告不符。
下列程式碼將示範 CS8764:
public class B
{
public virtual string GetMessage(string id) => string.Empty;
}
public class D : B
{
public override string? GetMessage(string? id) => default;
}
上述範例顯示基底類別的 virtual
方法和具有不同可為 null 性的 override
。 基底類別會傳回不可為空的字串,但衍生類別會傳回可為空的字串。 若反轉 string
和 string?
則會允許,因為衍生類別的限制更嚴格。 同理可證,參數宣告應相符。 即使基底類別未允許 Null,覆寫方法中的參數也可允許。
其他情況可能會產生這些警告。 介面方法宣告和該方法的實作不符。 或者委派類型與該委派對應的表達式不同。 類型參數和類型自變數在 Null 性上有所不同。
若要修正這些警告,請更新適當的宣告。
程式碼不符合屬性宣告
前面章節討論如何使用 屬性用於可為 Null 的靜態分析,以告知編譯器了解程式碼中的 Null 語意。 若程式碼不符合該屬性的 Promise,編譯器會警告您:
-
CS8607 - 可能的 Null 值不能用於標示
[NotNull]
或[DisallowNull]
的型別 -
CS8763 - 標示
[DoesNotReturn]
的方法不應傳回。 -
CS8770 - 方法缺少
[DoesNotReturn]
標註,無法與實作或覆寫的成員相符。 - CS8774 - 成員在離開時必須有非 null 值。
- CS8775 - 成員當結束時必須有非 null 值。
- CS8776 - 成員不可用於此屬性中。
- CS8777 - 參數在結束時必須有非空值。
- CS8824 - 參數在結束時必須有非 Null 值,因為參數為非 Null。
- CS8825 - 因為參數不是 Null,所以傳回值必須非 Null。
請考慮下列 方法:
public bool TryGetMessage(int id, [NotNullWhen(true)] out string? message)
{
message = null;
return true;
}
編譯器會產生警告,因為 message
參數被指派為 null
,且該方法會傳回 true
。
NotNullWhen
屬性表示不應發生。
若要解決這些警告,請更新您的程序代碼,使其符合所套用屬性的預期。 您可以變更屬性或演算法。
詳盡切換表達式
Switch 運算式必須詳盡,表示必須處理所有輸入值。 即使是不可為 Null 的參考型別,也必須考慮 null
值。 編譯器會在未處理 Null 值時發出警告:
- CS8655 - switch 運算式無法處理某些 null 輸入(並未涵蓋所有可能的輸入)。
- CS8847 - Switch 運算式無法處理某些空值輸入,這表示它並不詳盡。不過,具備『when』子句的模式可能會成功匹配此值。
下列範例程式碼示範此狀況:
int AsScale(string status) =>
status switch
{
"Red" => 0,
"Yellow" => 5,
"Green" => 10,
{ } => -1
};
輸入運算式為 string
,而不是 string?
。 編譯器仍會產生這個警告。
{ }
模式會處理所有非 Null 值,但不符合 null
。 若要解決這些錯誤,您可新增明確的 null
案例,或將 { }
取代為 _
(捨棄) 模式。 除了任何其他值之外,捨棄模式也會比對 Null。