CodeGym /課程 /C# SELF /required 屬性和

required 屬性和 field 屬性

C# SELF
等級 17 , 課堂 3
開放

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 的使用者一定要給值——大部分預設建構子都會把欄位設成預設值(null0 之類)。

那要怎麼強迫自己或其他工程師不要忘記給值?這就是為什麼 required 修飾詞在 C# 11 被發明出來啦。

2. required 屬性:嚴格初始化

required 修飾詞就是告訴編譯器,這個物件的某個屬性在建立時一定要被設定。簡單說,如果沒初始化這個屬性,編譯器直接不讓你過,IDE 也會畫一條超恐怖的紅色波浪線。


public class User
{
    public required string Name { get; set; }
    public int Age { get; set; }
}
required 的必填屬性

來試試建立一個使用者:

// 編譯錯誤: 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 更精簡。

getset 的範例

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 跟 constreadonly 一起用。
這些修飾詞不能一起用——required 只能用在一般屬性。硬要這樣寫會直接報錯。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION