readonly 欄位與 struct 防禦性複製陷阱
在看 Framework Design Guidelines 時,讀到官方把 Nullable<T> 改成 readonly 的過程中,曾意外引發一個不易察覺的問題。也因此我才注意到防禦性複製(defensive copy)這個議題,以及它帶來的陷阱。
相關討論串在 dotnet/corefx PR #24997
防禦性複製
詳見外部參考 C# struct 的防禦性複製(defensive copy)。
問題展示
以 GitHub 的案例,完整程式碼 Nullable<T> 裁剪後在 value 欄位加上 readonly 如下:
1 | public struct FakeNullable<T> where T : struct |
原因在 FakeNullable<T>.ToString() 這行:
1 | return value.ToString(); |
value 是 readonly field。對唯讀欄位呼叫 instance method 時,編譯器會先做一份防禦性複製,再對複本呼叫方法,概念上接近:
1 | var temp = value; |
所以 Counter.ToString() 改到的是複本 temp.Count,不是原本的 value.Count。這就是為什麼 counter.ToString() 執行後,counter.Value.Count 仍然是 0。
對照原本非唯讀版本產生的 IL Code,可以更清楚看出差異:
1 | public struct FakeNullable<T> where T : struct |
結論
防禦性複製是編譯器時期額外加入的操作,不特別留意的話很容易忽視。
參考
AI Tools
Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .Net Libraries, 3/e