首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 空值判斷完全指南:null、String.Empty、DBNull 與可為 Null 型別深度解析

     編輯:關於C#

前言

空值判斷 是 C# 開發中最基礎也最容易出錯的環節。null、""、string.Empty、DBNull.Value、Nullable<T> 等概念混淆,常導致 NullReferenceException、資料庫存取錯誤或邏輯缺陷。本文系統整理各類空值表示、判斷方式、效能比較與最佳實務。

四大空值類型對照表

類型適用對象記憶體行為常見場景
null參考型別、可為 Null 的實值型別參考指向「無物件」物件未初始化、資料庫 NULL 欄位
"" / string.Empty字串型別指向長度為 0 的字串物件使用者未輸入、選擇性欄位
DBNull.ValueADO.NET 資料庫欄位單一靜態實例,代表「資料庫 NULL」DataReader、DataRow 欄位值
Nullable<T> (T?)實值型別 (int, bool, DateTime...)結構體包裝,HasValue=false 表無值資料庫對應、選擇性參數

字串空值判斷:三種寫法效能比較

string s = GetString();

// 效能由高到低:
// 1. 長度檢查 (最快,避免建立比較字串)
if (s.Length == 0) { ... }

// 2. 靜態 Empty 比較 (編譯器優化,極接近 1)
if (s == string.Empty) { ... }

// 3. 常值比較 (會建立字串常值,極微小差異)
if (s == "") { ... }

// ⚠️ 危險:未檢查 null 直接存取 Length 會拋出 NullReferenceException
// 安全寫法:
if (s != null && s.Length == 0) { ... }
if (!string.IsNullOrEmpty(s)) { ... }  // .NET 2.0+ 推薦
if (!string.IsNullOrWhiteSpace(s)) { ... } // .NET 4.0+ 推薦 (含空白字元)

效能測試參考 (1 億次迴循)

方法相對耗時備註
s.Length == 01.00x (基準)最快,但需先確認非 null
s == string.Empty1.02x編譯器優化為相同 IL
s == ""1.03x極微小差異,可忽略
string.IsNullOrEmpty(s)1.15x含 null 檢查,最安全實用

null vs "" vs string.Empty 關鍵差異


// 記憶體圖示:
string strNull = null;        // 參考指向 無 (0x00000000)
string strEmpty = "";         // 參考指向 字串物件 (長度=0)
string strEmpty2 = string.Empty; // 同一個靜態空字串物件 (字串駐留)

// 行為差異:
strNull.Length;      // ❌ NullReferenceException
strEmpty.Length;     // ✅ 0
strNull.ToString();  // ❌ NullReferenceException
strEmpty.ToString(); // ✅ ""

// 判斷邏輯:
strNull == "";           // false
strNull == string.Empty; // false
string.IsNullOrEmpty(strNull); // true

DBNull 深度解析:資料庫 NULL 的特殊表示

為什麼需要 DBNull?

.NET 的實值型別 (int, DateTime, bool...) 無法直接表示「無值」。ADO.NET 使用 object 儲存欄位值,引入 DBNull 類別 (單例模式) 專門代表「資料庫 NULL」。

正確判斷與轉換模式

// ❌ 錯誤:直接 ToString() 會拋例外 (DBNull 無法轉型)
string name = row["Name"].ToString(); 

// ✅ 正確:先判斷 DBNull
object val = row["Name"];
string name = (val == DBNull.Value) ? null : val.ToString();

// ✅ 簡潔寫法 (C# 7.0+ 模式比對)
string name = row["Name"] is DBNull ? null : row["Name"].ToString();

// ✅ 擴充方法封裝 (推薦用於專案通用)
public static class DataRowExtensions
{
    public static T GetValueOrDefault<T>(this DataRow row, string column, T defaultValue = default)
    {
        object val = row[column];
        if (val == DBNull.Value || val == null) return defaultValue;
        return (T)Convert.ChangeType(val, typeof(T));
    }
}

// 使用:
int age = row.GetValueOrDefault("Age", 0);
string name = row.GetValueOrDefault("Name", "");
DateTime? birthday = row.GetValueOrDefault<DateTime?>("Birthday");

Convert.IsDBNull() vs DBNull.Value.Equals()

object val = row["Column"];

// 兩者等價,Convert.IsDBNull 內部即呼叫 DBNull.Value.Equals
bool isNull1 = Convert.IsDBNull(val);
bool isNull2 = DBNull.Value.Equals(val);

// 注意:val == DBNull.Value 也可行 (DBNull 覆寫 Equals),但模式比對更清晰

可為 Null 的實值型別 (Nullable<T> / T?)

語法與核心屬性

int? age = null;           // 等同 Nullable<int> age = null;
int? age2 = 25;

Console.WriteLine(age.HasValue);    // false
Console.WriteLine(age2.HasValue);   // true
Console.WriteLine(age.Value);       // ❌ InvalidOperationException (無值時存取 Value)
Console.WriteLine(age2.Value);      // 25

// 安全取值:
int value = age.GetValueOrDefault();      // 0 (預設值)
int value2 = age.GetValueOrDefault(-1);   // -1 (自訂預設)
int value3 = age ?? -1;                   // -1 (null 聯合運算子)

運算與比較規則

int? a = 10;
int? b = null;

Console.WriteLine(a + 5);       // 15 (自動解包運算)
Console.WriteLine(b + 5);       // null (傳播 null)
Console.WriteLine(a == b);      // false
Console.WriteLine(b == null);   // true
Console.WriteLine(a > b);       // false (任何與 null 比較皆為 false)

// 處理邏輯:
int result = (a ?? 0) + (b ?? 0);  // 10

實務場景決策表

場景推薦判斷方式程式碼範例
檢查字串是否有輸入內容string.IsNullOrWhiteSpace()if (!string.IsNullOrWhiteSpace(txtInput.Text))
檢查字串非 null 且非空string.IsNullOrEmpty()if (!string.IsNullOrEmpty(s))
資料庫字串欄位可能為 NULLis DBNull + ?? 運算子string name = row["Name"] as string ?? "";
資料庫數值欄位可能為 NULLNullable<T> + GetValueOrDefaultint? age = row["Age"] as int?; int v = age ?? 0;
方法選擇性參數Nullable<T> 或 預設參數void Foo(int? id = null) / void Foo(int id = 0)
物件參考可能為 null模式比對 / ?. 運算子if (obj is MyClass m) { } / obj?.Method()

現代 C# (7.0+) 空值處理新語法

1. Null 條件運算子 ?. 與 ?[]

// 傳統寫法
if (user != null && user.Address != null)
    Console.WriteLine(user.Address.City);

// 現代寫法
Console.WriteLine(user?.Address?.City);  // 任一為 null 結果為 null

// 陣列/集合安全存取
var first = list?[0];  // list 為 null 時回傳 null

2. Null 聯合運算子 ?? 與 ??=

// 提供預設值
string name = inputName ?? "Guest";

// C# 8.0: 僅在左邊為 null 時指派
cache ??= new Dictionary<string, object>();

3. 模式比對 is null / is not null

if (obj is null) { }           // 比 obj == null 更安全 (避免運算子多載)
if (obj is not null) { }       // C# 9.0
if (obj is string s) { }       // 類型檢查 + 非 null 綁定

4. 可為 Null 參考型別 (NRT, C# 8.0+)


// 專案檔啟用:enable

// 非可為 null (預設)
string name = "John";   // 編譯器保證不為 null
// name = null;         // ⚠️ 編譯警告 CS8600

// 明確可為 null
string? optional = null; // 允許 null
optional = "Value";

// 靜態分析輔助開發者預防 NullReferenceException

常見陷阱與除錯技巧

陷阱錯誤現象解決方案
字串未初始化直接存取 LengthNullReferenceException使用 string.IsNullOrEmpty() 或 ?.Length
DataRow 欄位為 DBNull 直接轉型InvalidCastException先判斷 DBNull.Value,使用 as 或模式比對
Nullable<T> 直接存取 ValueInvalidOperationException先檢查 HasValue,或用 GetValueOrDefault/??
集合為 null 直接 foreachNullReferenceException初始化為空集合 new List<T>(),或用 ?. 運算子
事件無訂閱者直接觸發NullReferenceException使用 ?.Invoke() 或預設委派 = delegate { }

總結

C# 空值判斷的核心在於 區分「參考無物件」(null)、「空字串」("")、「資料庫 NULL"(DBNull)、「實值型別無值"(Nullable<T>) 四種語意不同的空值。現代 C# 提供 string.IsNullOrWhiteSpace、?.、??、is null、Nullable 參考型別 等強大工具,配合良好的防禦性程式設計習慣 (初始化集合、封裝資料庫存取、啟用 NRT),能從根源消除 90% 以上的空值相關 Bug。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved