解決可為 Null 的警告
本文涵蓋下列編譯器警告:
- CS8597 - 擲回值可能為 Null。
- CS8600 - 正在將 Null 常值或可能的 Null 值轉換為不可為 Null 的型別。
- CS8601 - 可能有 Null 參考指派。
- CS8602 - 可能有 Null 參考的取值。
- CS8603 - 可能有 Null 參考傳回。
- CS8604 - 參數可能有 Null 參考引數。
- CS8605 - Unboxing 可能為 Null 值。
- CS8607 - 可能的 Null 值不能用於標示
[NotNull]
或[DisallowNull]
的型別 - CS8608 - 型別中參考型別的可 Null 性與覆寫的成員不符合。
- CS8609 - 傳回型別中參考型別的可 Null 性與覆寫的成員不符合。
- CS8610 - 參數型別中參考型別的可 Null 性與覆寫的成員不符合。
- CS8611 - 參數型別中參考型別的可 Null 性與部分方法宣告不符合。
- CS8612 - 型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8613 - 傳回型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8614 - 參數型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8615 - 型別中參考型別的可 Null 性與實作的成員不符合。
- CS8616 - 傳回型別中參考型別的可 Null 性與實作的成員不符合。
- CS8617 - 參數型別中參考型別的可 Null 性與實作的成員不符合。
- CS8618 - 非 Null 變數在結束建構函式時必須包含非 Null 值。請考慮將其宣告為可 Null。
- CS8619 - 值中參考型別的可 Null 性與目標型別不符合。
- CS8620 - 因為參考型別的可 Null 性有所差異,所以引數無法用於參數。
- CS8621 - 傳回型別中參考型別的可 Null 性與目標委派不相符 (可能的原因是可 Null 性屬性)。
- CS8622 - 參數類型中參考型別的可 Null 性與目標委派不相符 (可能的原因是可 Null 性屬性)。
- CS8624 - 因為參考型別的可 Null 性有所差異,所以引數無法作為輸出。
- CS8625 - 無法將 Null 常值轉換成不可為 Null 的參考型別。
- CS8629 - 可為 Null 的實值型別可能為 Null。
- CS8631 - 類型在泛型型別或方法中不能當做類型參數使用。型別引數可 NULL 性不符合條件約束類型。
- CS8633 - 方法的型別參數之條件約束的可 NULL 性不符合介面方法的型別參數之條件約束。請考慮改用明確的介面實作。
- CS8634 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「類別」條件約束。
- CS8643 - 明確介面指定名稱中參考類型可 Null 性與類型所實作的介面不相符。
- CS8644 - 類型未實作介面成員。基底類型所實作之介面中的參考類型的可 NULL 性不相符。
- CS8645 - 成員已列在類型上的介面清單中,並具有不同的參考類型可 Null 性。
- CS8655 - Switch 運算式不會處理某些 Null 輸入 (並不詳盡)。
- CS8667 - 部分方法宣告在類型參數的限制式中,有不一致的可 Null 性。
- CS8670 - 物件或集合初始設定式意味會解除參考可能為 Null 的成員。
- CS8714 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「非 NULL」條件約束。
- CS8762 - 參數在結束時必須有非 Null 值。
- CS8763 - 標示
[DoesNotReturn]
的方法不應傳回。 - CS8764 - 傳回型別是否可為 Null 的情況,與覆寫的成員不相符 (可能的原因是屬性可為 Null)。
- CS8765 - 參數類型是否可為 Null 的情況,與覆寫的成員不相符 (可能的原因是屬性可為 Null)。
- CS8766 - 傳回型別中參考型別是否可為 Null 的情況,與隱含實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8767 - 參數類型中參考型別是否可為 Null 的情況,與隱含實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8768 - 傳回型別中參考型別是否可為 Null 的情況,與實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8769 - 參數類型中參考型別是否可為 Null 的情況,與實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8770 - 方法缺少
[DoesNotReturn]
註釋,與實作或覆寫的成員不相符。 - CS8774 - 成員在結束時必須有非 Null 值。
- CS8776 - 成員不可用於此屬性中。
- CS8775 - 成員在結束時必須有非 Null 值。
- CS8777 - 參數在結束時必須有非 Null 值。
- CS8819 - 傳回型別中參考型別的可 Null 性與部分方法宣告不符。
- CS8824 - 參數在結束時必須有非 Null 值,因為參數為非 Null。
- CS8825 - 因為參數不是 Null,所以傳回值必須非 Null。
- CS8847 - Switch 運算式不會處理某些 Null 輸入 (並不詳盡)。不過,具有『when』子句的模式可能會成功比對此值。
可為 Null 警告的目的,即是盡可能降低應用程式執行時擲回 System.NullReferenceException 的機會。 為了達成此目標,當程式碼的建構有可能會導致 Null 參考例外狀況時,編譯器會使用靜態分析和發出警告。 您可套用型別註釋和屬性,為編譯器提供靜態分析的資訊。 這些註釋和屬性描述型別引數、參數和成員的可 Null 性。 在本文中,您將了解不同的技術,以解決編譯器靜態分析產生的可為 Null 警告。 此處所述的技術適用於一般 C# 程式碼。 請參閱使用可為 Null 的參考型別,了解如何使用可為 Null 的參考型別和 Entity Framework 核心。
您可使用四種技術之一來解決幾乎所有警告:
- 新增必要的 Null 檢查。
- 新增
?
或!
可為 Null 的註釋。 - 新增描述 Null 語意的屬性。
- 正確初始化變數。
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 再取值。 集合初始設定式警告可能較難找出。 在初始設定式將元素加入集合時,編譯器會偵測該集合可能為 Null。
在許多情況下,您可在取值前先檢查變數不是 Null,以修正這些警告。 請考慮在取消引用 message
參數之前新增 Null 檢查的下列專案:
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 的私人公用程式方法。 編譯器不知道這項方法提供 Null 檢查。 請考慮使用私人公用程式方法 IsNotNull
的下列範例:
public void WriteMessage(string? message)
{
if (IsNotNull(message))
Console.WriteLine(message.Length);
}
編譯器會警告,當您撰寫屬性 message.Length
時可能會取值 null,因為其靜態分析判斷 message
可能為 null
。 您可能知道 IsNotNull
提供 Null 檢查,且傳回 true
時,Null 狀態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 會通知編譯器,當方法傳回 true
時,用於 obj
參數的引數非 Null。 當方法傳回 false
時,引數的 Null 狀態與呼叫方法前的狀態相同。
提示
您可使用一系列豐富屬性,以描述方法和屬性對 Null 狀態的影響。 如需深入了解,您可參閱語言參考文章中的可為 Null 的靜態分析屬性。
修正可能為 Null 變數取值的警告時,包含三種技術之一:
- 新增遺漏的 Null 檢查。
- 在 API 上新增 Null 分析屬性,以影響編譯器的 Null 狀態靜態分析。 呼叫方法後,這些屬性會通知編譯器,傳回值或引數應可能為 Null 或非 Null。
- 將 Null 容許運算子
!
套用至運算式,強制狀態為非 Null。
已將可能的 Null 指派給不可為 Null 的參考
此警告組合會警示,您正在將不可為 Null 的變數類型指派給 Null 狀態可能為 Null 的運算式。 這些警告如下:
- CS8597 - 擲回值可能為 Null。
- CS8600 - 正在將 Null 常值或可能的 Null 值轉換為不可為 Null 的型別。
- CS8601 - 可能有 Null 參考指派。
- CS8603 - 可能有 Null 參考傳回。
- CS8604 - 參數可能有 Null 參考引數。
- CS8605 - Unboxing 可能為 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。 右側的運算式可在指派前檢查 Null,如下列範例所示:
string notNullMsg = TryGetMessage(42) ?? "Unknown message id: 42";
上述範例示範方法的傳回值指派。 您可標註方法 (或屬性),以表示方法傳回非 Null 值的時間。 輸入引數非 Null 時,System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute 通常會將傳回值指定為非 Null。 另一個替代方法是將 Null 容許運算子 !
新增至右側:
string msg = TryGetMessage(42)!;
修正可能為 Null 運算式指派給非 Null 變數的警告時,包含四種技術之一:
- 將指派的左側變更為可為 Null 的類型。 當您取值該變數時,此動作可能會產生新的警告。
- 先提供 Null 檢查再指派。
- 標註會產生指派右側的 API。
- 將 Null 容許運算子新增至指派的右側。
不可為 Null 的參考未初始化
此警告組合會警示,您正在將不可為 Null 的變數類型指派給 Null 狀態可能為 Null 的運算式。 這些警告如下:
- CS8618 - 非 Null 變數在結束建構函式時必須包含非 Null 值。請考慮將其宣告為可 Null。
- CS8762 - 參數在結束時必須有非 Null 值。
請考慮使用下列範例類別:
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;
}
另一個可能的替代方案,則是將這些成員變更為可為 Null 的參考型別。 若名稱應允許 null
,則可定義 Person
類別如下:
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!
。 這可告知編譯器屬性已設為非 Null 值。 事實上,基底 DbContext
會執行該集合的初始化。 編譯器的靜態分析不會挑選該集合。 如需可為 Null 參考類型和 Entity Framework Core 的詳細資訊,請參閱使用 EF Core 中可為 Null 的參考型別一文。
修正不可為 Null 成員未初始化的警告時,包含四種技術之一:
- 變更建構函式或欄位初始設定式,以確保所有不可為 Null 的成員皆已初始化。
- 將一或多個成員變更為可為 Null 的類型。
- 標註任何協助程式方法,已表示指派的成員。
- 將初始設定式新增至
null!
,表示該成員在其他程式碼中初始化。
可 Null 性宣告不符
許多警告會表示方法、委派或型別參數的特徵標記可 Null 性不符。
- CS8608 - 型別中參考型別的可 Null 性與覆寫的成員不符合。
- CS8609 - 傳回型別中參考型別的可 Null 性與覆寫的成員不符合。
- CS8610 - 參數型別中參考型別的可 Null 性與覆寫的成員不符合。
- CS8611 - 參數型別中參考型別的可 Null 性與部分方法宣告不符合。
- CS8612 - 型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8613 - 傳回型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8614 - 參數型別中參考型別的可 Null 性與隱含實作的成員不符合。
- CS8615 - 型別中參考型別的可 Null 性與實作的成員不符合。
- CS8616 - 傳回型別中參考型別的可 Null 性與實作的成員不符合。
- CS8617 - 參數型別中參考型別的可 Null 性與實作的成員不符合。
- CS8619 - 值中參考型別的可 Null 性與目標型別不符合。
- CS8620 - 因為參考型別的可 Null 性有所差異,所以引數無法用於參數。
- CS8621 - 傳回型別中參考型別的可 Null 性與目標委派不相符 (可能的原因是可 Null 性屬性)。
- CS8622 - 參數類型中參考型別的可 Null 性與目標委派不相符 (可能的原因是可 Null 性屬性)。
- CS8624 - 因為參考型別的可 Null 性有所差異,所以引數無法作為輸出。
- CS8631 - 類型在泛型型別或方法中不能當做類型參數使用。型別引數的可 NULL 性不符合條件約束類型。
- CS8633 - 方法的型別參數之條件約束的可 NULL 性不符合介面方法的型別參數之條件約束。請考慮改用明確的介面實作。
- CS8634 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「類別」條件約束。
- CS8643 - 明確介面指定名稱中參考類型可 Null 性與類型所實作的介面不相符。
- CS8644 - 類型未實作介面成員。基底類型所實作之介面中的參考類型的可 NULL 性不相符。
- CS8645 - 成員已列在類型上的介面清單中,並具有不同的參考類型可 Null 性。
- CS8667 - 部分方法宣告在類型參數的限制式中,有不一致的可 Null 性。
- CS8714 - 類型在泛型型別或方法中不能當做型別參數使用。型別引數的可 NULL 性不符合「非 NULL」條件約束。
- CS8764 - 傳回型別是否可為 Null 的情況,與覆寫的成員不相符 (可能的原因是屬性可為 Null)。
- CS8765 - 參數類型是否可為 Null 的情況,與覆寫的成員不相符 (可能的原因是屬性可為 Null)。
- CS8766 - 傳回型別中參考型別是否可為 Null 的情況,與隱含實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8767 - 參數類型中參考型別是否可為 Null 的情況,與隱含實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8768 - 傳回型別中參考型別是否可為 Null 的情況,與實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8769 - 參數類型中參考型別是否可為 Null 的情況,與實作的成員不相符 (可能的原因是屬性可為 Null)。
- CS8819 - 傳回型別中參考型別的可 Null 性與部分方法宣告不符。
下列程式碼將示範 CS8764:
public class B
{
public virtual string GetMessage(string id) => string.Empty;
}
public class D : B
{
public override string? GetMessage(string? id) => default;
}
上述範例顯示基底類別的 virtual
方法和 override
具有不同 Null 性。 基底類別會傳回不可為 Null 的字串,但衍生類別會傳回可為 Null 的字串。 若反轉 string
和 string?
則會允許,因為衍生類別的限制更嚴格。 同理可證,參數宣告應相符。 即使基底類別未允許 Null,覆寫方法中的參數也可允許。
其他情況可能會產生這些警告。 介面方法宣告和該方法的實作可能不符。 或者委派類型和該委派的運算式可能不同。 型別參數和型別引數的可 Null 性差異可能不同。
若要修正這些警告,請更新適當的宣告。
程式碼不符合屬性宣告
上述各節已討論如何使用屬性進行可為 Null 的靜態分析,以通知編譯器程式碼的 Null 語意。 若程式碼不符合該屬性的 Promise,編譯器會警告您:
- CS8607 - 可能的 Null 值不能用於標示
[NotNull]
或[DisallowNull]
的型別 - CS8763 - 標示
[DoesNotReturn]
的方法不應傳回。 - CS8770 - 方法缺少
[DoesNotReturn]
註釋,與實作或覆寫的成員不相符。 - CS8774 - 成員在結束時必須有非 Null 值。
- CS8775 - 成員在結束時必須有非 Null 值。
- CS8776 - 成員不可用於此屬性中。
- CS8777 - 參數在結束時必須有非 Null 值。
- 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 運算式
Switch 運算式必須詳盡,表示必須處理所有輸入值。 即使是不可為 Null 的參考型別,也必須考慮 null
值。 編譯器會在未處理 Null 值時發出警告:
- CS8655 - Switch 運算式不會處理某些 Null 輸入 (並不詳盡)。
- CS8847 - Switch 運算式不會處理某些 Null 輸入 (並不詳盡)。不過,具有『when』子句的模式可能會成功比對此值。
下列範例程式碼示範此狀況:
int AsScale(string status) =>
status switch
{
"Red" => 0,
"Yellow" => 5,
"Green" => 10,
{ } => -1
};
輸入運算式為 string
,而不是 string?
。 編譯器仍會產生這個警告。 { }
模式會處理所有非 Null 值,但不符合 null
。 若要解決這些錯誤,您可新增明確的 null
案例,或將 { }
取代為 _
(捨棄) 模式。 捨棄模式會比對 Null 及任何其他值。