NullReferenceException:一場與不存在的對話
窗外開始下雨了,輕輕敲在窗上的雨聲,像記憶敲著玻璃,提醒你今天又過去了一天。
房間裡只亮著一盞檯燈,桌上的咖啡溫度早已散去。你盯著螢幕,一行行程式碼像星辰般閃爍,帶著某種秩序,也藏著未解的謎題。
這時候,它又來了。
System.NullReferenceException, Object reference not set to an instance of an object.
我滄桑的搖了搖頭,示意自己又大意了一回
🏔️ NullReferenceException
當你試圖透過一個根本沒有指向任何物件的參考(null)來存取資料或方法,.NET 就會丟出 NullReferenceException。
每一個「物件變數」本質上是一個指向記憶體位置的參考(Reference)。
舉例來說:
1 |
|
person 就像你手上拿是地址(像 Google Maps 上的定位點),而 person.Name 表示請你開車去那個地址,然後進門找「那個人的名字」,如果 person = null,則相當於,手上的地圖定位點是一坨空白,登愣!
🏔️ 維護團隊專案的心態
對物件的存在永遠不要理所當然,設計時應「懷疑一切可能為 null」,現實常見的情境如
- 從資料庫抓不到資料卻直接使用
- DI 注入失敗或沒註冊物件
- JSON deserialize 成 null
- Service 回傳 null 未做檢查
以及其他花式資料遺失的情境,當你 Exception 碰久了,會開始漸漸築起警戒心,但這也不是說,都是誰的錯,而是設計程式隨時需要思考資料的處理流向與處理的必要性,小小的 person.Name ,我們可能需要想,資料怎麼來的,又要怎麼去,是使用者輸入有誤嗎,我該抓出錯誤回報什麼錯誤訊息?…
在面對 Null Reference Issue 時,我們可以透過不同的面向迎擊
例如專案可以開啟 Nullable Reference Types,編譯器會跑出那些小蚯蚓來提示你程式碼的哪個部位可能有 null
撰寫邏輯時,如何透過語法糖的使用例如 ?.(Null Conditional Operator) 和 ?? (Null Coalescing Operator) 作為安全存取與設定預設值的工具
總而言之,我們要避免過度信任外部資料,API、DB 回傳的值要小心檢查、建構函式就初始化好物件 不要等到用的時候才想起來、運用 DI(Dependency Injection)時,明確指出依賴,並善用單元測試,檢查極端與例外情況,提早發現問題
🏔️ 到處用 ?. 安心嗎?還是潛藏危機?
在寫 C# 的時候,可以用 ?.(null conditional operator)來「擋錯」,一不小心就寫了一串:
1 |
|
看起來好像「防錯做滿了」,但這真的好嗎?
🔹 回到問題的本質,我們應該問的是:
「為什麼這個東西會是 null?」
而不是
「怎麼樣加 ?. 才不會噴錯?」
合理使用 ?. 的情境,是你明確知道某個物件有可能是 null,例如:
- 來自外部 API 回傳的資料
- 資料庫查詢結果(尤其是左外連接)
- 前端表單的非必填欄位
- Cookie、Session、QueryString 的值
這些都是你無法掌控來源時,安全防禦的一環。
⚠️ 不建議無腦加 ?. 的狀況
1 |
|
原本是預期:沒有 Coupon 就不要打折
但問題來了:有些 order 資料在還沒綁定前,Coupon 是 null,應該套用預設折扣
但有時候是訂單不知道為甚麼資料不見了卻還是套用預設折扣,反而是走到後面然後資料不齊全到狀況
我們改成更清楚、具判斷意圖的寫法:
1 |
|
我們是否不小心用 null propagation 來模糊掉異常狀態,反而導致後續釐清問題的困難
🏔️ 預設值
當我們已經確定某個資料為 null 是合理的情況,也就是說:
「這筆資料可以沒有,即使缺失也不應影響整體流程。」
這時候,使用預設值(例如 ?? 或 GetValueOrDefault())來處理,就是一種非常合適的容錯策略。
1 |
|
這段程式碼的意圖是:在未登入時顯示「訪客」。這屬於正常的 UX fallback,因為匿名瀏覽是被允許的情境。
但前提是 —— 你已確認 user == null 就代表「未登入」,而不是系統異常或資料遺失。如果你清楚這點,那麼這樣使用 ?? 是合理的。否則可能發生使用者登入,結果畫面還是顯示訪客的情況!
再來一個例子
在行銷活動中,有些活動可能只設條件與優惠,而沒有填寫說明文字。這樣的缺值在業務上是被接受的。此時給一個親切的預設值,如「無備註」,會讓 UI 更完整,也不會誤導使用者。
1 |
|
⚠️ 注意:有些人會習慣「看到 null 就補值」,但如果這個補的值沒有業務語意,只為了「不報錯」,反而容易造成誤導。
1 |
|
你知道為什麼 TotalAmount 是 null 嗎?
是訂單根本還沒完成?還是金額真的為 0?
這樣 fallback 可能讓後續報表失真、財務混亂!
🏔️ string interpolation
當我們在使用 字串插值(string interpolation) 的語法,例如 $”{xxx}”,它看起來像是語法糖,但實際上背後經過了編譯器的轉換與優化。如果插入的是 nullable value type(例如 int?、bool?、DateTime?),那麼整個流程會非常貼心地自動處理 null 的情況,避免 NullReferenceException 發生。
我們看一個例子:
1 |
|
它不會拋出例外,也不會印出 “null”。這是因為 C# 的編譯器會在背後幫你處理成這樣的邏輯:
1 |
|
也就是說等於
1 |
|
雖然我們知道做輸出時,會 ToString, 但沒想到他會幫你做防呆吧!
🏔️ HasValue & .Value
使用 Nullable
在底層一點點,Nullable
1 |
|
這段程式碼告訴我們一件重要的事:
👉 你不能無條件地使用 .Value,否則可能會拋出 InvalidOperationException。
在你準備使用 .Value 前,應該先使用 HasValue 做保險。
1 |
|
還有一招是
1 |
|
🏔️ is null == null
基本語意是這樣
1 |
|
🔹 x == null
這是一個「運算子多載(operator overloading)」的呼叫。
也就是說,如果 x 是某個類別的實例,而這個類別有自定義 operator ==,那麼執行的就是該方法。意味著 == null 可以被重寫、被干擾!
🔹 x is null
這是 C# 7.0 以後提供的 Pattern Matching Null Test,是一種語法層級的 null 判斷,不會被 operator 多載影響。
也就是說,它永遠是單純的「這個參考是否為 null」。
x is null 是語言本身的保證;x == null 則是你或別人可以動手腳的合約。
☘️ 結語。
如何分辨 x == null 與 x is null 的本質差異?
為何 ?. 不是萬靈丹,而應該搭配清楚的判斷邏輯使用?
如何用 ?? 和 GetValueOrDefault() 做出真正貼合情境的預設值?
還有那個最根本的提醒 —— 在使用 .Value 前,請先問自己:這真的有值嗎?
在這 Debug 的深夜裡,我們怎麼與 null 相處
🕯窗外的雨停了,城市的輪廓在路燈下緩緩清晰。
你儲存了檔案,啜了口早已冰涼的咖啡,心裡卻因為解開一個小謎題而泛起微光。這不是一場戰鬥,而是一種與混亂共舞的優雅。
下一次遇見 Null,你會更有感覺,也更從容。🌙