1. 前言
用 C# 寫程式不用屬性也行啦,但就像穿著溜冰鞋在白宮裡面滑一樣——可以,但超不舒服。現在大家都習慣用簡短語法,不用多寫一堆 getter/setter。當我們的 app 開始處理「認真」的 model(比如 User 類別,描述系統裡的使用者),就很重要要確保所有需要的資料真的都有填。
舉例來說,如果我們有個類別:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
很容易就忘記初始化屬性:
User user = new User(); // Name 會是 null,Age = 0
結果你可能在十個畫面、一百個邏輯轉彎之後,遇到超有名的 NullReferenceException,然後找 bug 找到懷疑人生。
回憶一下 init-only 屬性
對啦,我們可以用初始化時才能設定的語法:
public string Name { get; init; }
但就算這樣,沒有人會強迫 class 的使用者一定要給值——大部分預設建構子都會把欄位設成預設值(null、0 之類)。
那要怎麼強迫自己或其他工程師不要忘記給值?這就是為什麼 required 修飾詞在 C# 11 被發明出來啦。
2. required 屬性:嚴格初始化
required 修飾詞就是告訴編譯器,這個物件的某個屬性在建立時一定要被設定。簡單說,如果沒初始化這個屬性,編譯器直接不讓你過,IDE 也會畫一條超恐怖的紅色波浪線。
public class User
{
public required string Name { get; set; }
public int Age { get; set; }
}
來試試建立一個使用者:
// 編譯錯誤: Name 屬性一定要初始化!
User user1 = new User();
或者這樣:
// 編譯錯誤: 沒有指定 required 屬性 Name
User user2 = new User { Age = 18 };
正確的寫法:
User user3 = new User { Name = "赫敏", Age = 18 };
required 怎麼運作?
required 修飾詞就是跟編譯器說:「物件建構子跑完後(不管哪個建構子!),這個屬性一定要被設定。」
這對一般屬性跟 init-only 都適用:
public required string Name { get; init; }
如果你有自訂建構子,裡面有初始化 required 屬性,那也沒問題。
常見檢查方式
| 情境 | 編譯器 OK 嗎? |
|---|---|
| 沒設定 required 屬性 | ❌ |
| 在物件初始化器裡設定 | ✔️ |
| 在建構子裡設定 | ✔️ |
我們的「狗狗」model 範例
public class Dog
{
public required string Name { get; set; }
public int Age { get; set; }
}
Dog dog = new Dog { Name = "波比", Age = 5 }; // OK!
Dog badDog = new Dog { Age = 2 }; // 錯誤!沒指定 Name
required 真的有用的地方?
- DTO 傳遞: 如果你有 API,要確保所有必要欄位都有傳進來。
- 複雜 model 必填屬性: 比如 Product 一定要有 SKU,Order 一定要有編號。
- 面試或 code review: 你秀出這種語法,大家都會覺得你很 pro(還有點羨慕)。
3. required 跟建構子怎麼配合?
有時候你會自己寫建構子。如果沒在建構子或物件初始化器裡初始化 required 屬性,編譯器就會報錯。
public class Article
{
public required string Title { get; set; }
public required string Author { get; set; }
public Article()
{
// 如果沒初始化 Title 和 Author——編譯錯誤!
// 可以這樣:
Title = "無標題";
Author = "未知";
}
}
如果建構子自己有給 required 屬性值——沒問題。如果沒有,你就要用物件初始化器(new Article { ... })來補。
使用細節
- required 只能用在屬性,不能用在欄位。
- required 不會被繼承——如果 base class 有 required,子類沒寫 required,編譯器不會罵你(但建議還是明確寫出來)。
- required 不能用在自動欄位或其他不是 property 的東西。
4. 箭頭語法屬性
C# 新版讓大家越來越追求簡潔又有表現力的 code。其中一個很酷的新語法就是 箭頭屬性(或叫 expression-bodied properties)。
有時候你只想定義一個單純回傳值的屬性,沒啥額外邏輯。以前要寫完整的 getter 跟大括號:
public int Age
{
get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}
現在可以 超短——直接用箭頭 (=>):
public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;
這種語法叫 表達式屬性(expression-bodied property)。很適合簡單計算,讓 code 更精簡。
用 get 和 set 的範例
public class Book
{
private string _title;
public string Title
{
get => _title;
set => _title = value.Trim();
}
}
這裡 get 回傳欄位值,set 會先把多餘空白去掉再存進去。
只有 get(唯讀)的範例:
如果屬性是唯讀——可以完全不用大括號,直接用 =>。
public class Person
{
private string name = "馬克吐溫";
// 唯讀:計算屬性
public string Name => name.ToUpper();
}
這裡 Name 只能讀,永遠回傳 name 的大寫。
5. field 關鍵字用在屬性
C# 14 以前,如果你想在 setter 或 getter 裡直接存取自動屬性的 隱藏欄位(比如避免遞迴或加點自訂邏輯)——根本做不到,只能自己宣告欄位。
C# 14 讓你可以用 field 關鍵字直接存取自動屬性的隱藏欄位:
public class Person
{
public string Name
{
get => field; // field 就是 Name 屬性的隱藏欄位
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("名字不能是空的!");
field = value; // 用 field 取代明確的 _name
}
}
}
以前只能這樣寫:
private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("名字不能是空的!");
_name = value;
}
}
現在少宣告一個變數,code 更精簡就是爽。
為什麼有時候一定要在屬性裡存取欄位?
- 有時候你要明確控制 值怎麼存、存哪裡(比如要回傳物件複本、cache 結果或 lazy-loading)。
- 如果你要用 attribute 或 reflection——欄位名稱可能會用到。
- 有些(反)序列化或效能優化場景,你會想更細緻控制值的存放。
使用範例:
setter 裡做資料驗證
public double Grade
{
get => field;
set
{
if (value < 0 || value > 5)
throw new ArgumentOutOfRangeException("分數必須在 0 到 5 之間");
field = value;
}
}
變更統計
public int StepCount
{
get => field;
set
{
if (value > field)
{
Console.WriteLine($"耶!你多走了 {value - field} 步!");
}
field = value;
}
}
Lazy Load
public string Data
{
get
{
if (field == null)
field = LoadDataFromDatabase();
return field;
}
set => field = value;
}
6. 常見錯誤跟小細節
錯誤 1:忘記初始化 required 屬性。
編譯器不會讓你過,直接在 build 時報錯,幫你避免 runtime 的大災難。
錯誤 2:required 屬性沒在建構子完全初始化。
如果建構子沒給所有必填屬性值,編譯器會提醒你漏掉東西。
錯誤 3:想把 required 跟 const 或 readonly 一起用。
這些修飾詞不能一起用——required 只能用在一般屬性。硬要這樣寫會直接報錯。
GO TO FULL VERSION