在 C# 物件導向設計中,「欄位」儲存資料,「屬性」控制存取。雖然編譯器將屬性降級為 get_PropertyName/set_PropertyName 兩個方法,但語言層面的語法糖讓封裝變得優雅且安全。本文深入探討欄位修飾符、readonly vs const 差異、屬性的三種存取模式、繼承語意與進階模式。
obj.field)。TypeName.field),不可透過實例存取(編譯錯誤)。| 修飾符 | 可見範圍 |
|---|---|
public | 任意程式碼 |
protected | 本類別與衍生類別 |
internal | 同一組件內所有類別 |
protected internal | 同一組件內所有類別 + 衍生類別(跨組件亦可) |
private | 僅本類別 |
實務建議:欄位預設 private,對外暴露請改用屬性,符合「封裝」原則。
readonly vs const:編譯期 vs 執行期常數| 特性 | const | readonly |
|---|---|---|
| 初始化時機 | 宣告時(編譯期常數) | 宣告時或建構函式中(執行期常數) |
| 儲存位置 | 編譯期內嵌至呼叫端 IL(常數傳播) | 執行期讀取欄位值 |
| 型別限制 | 僅限實值型別、string、enum、null 參考 | 任意型別 |
| 靜態性 | 隱含 static | 可為實例或靜態 |
| 版本控制影響 | 修改值需重新編譯所有參考組件 | 僅需重新編譯定義組件 |
經典案例:跨組件常數共享時,若未來可能調整數值,請使用 static readonly 避免版本衝突。
// 讀寫屬性
public int Age { get; set; }
// 唯讀屬性(C# 6+ 可僅寫 get)
public string Name { get; }
// 唯寫屬性(極少見,通常用於 WriteOnly 集合)
public string Password { set; }
關鍵規則:同名屬性的 get/set 必須宣告在同一個屬性區塊內,不可分離(否則編譯錯誤 CS0111)。
private string _name;
public string Name
{
get => _name;
set => _name = string.IsNullOrWhiteSpace(value) ? "預設名稱" : value.Trim();
}
屬性本質是方法,可執行任意邏輯:快取、延遲初始化、事件通知、權限檢查等。
virtual、override、sealed、abstractpublic abstract class Animal
{
public virtual string Sound { get; } = "...";
public abstract string Name { get; set; }
}
public class Dog : Animal
{
public sealed override string Sound => "汪汪"; // 封鎖進一步覆寫
public override string Name { get; set; }
}
virtual:允許衍生類別 override(執行期繫結)。override:覆寫基類虛擬/抽象屬性。sealed override:覆寫並禁止後續衍生類別再次覆寫。abstract:強制衍生類別實作(類別亦須標記 abstract)。new:隱藏基類同名屬性(編譯期繫結,非多型)。static)僅能存取靜態欄位,呼叫方式 TypeName.Prop。extern)搭配 DllImport 對應非受控程式碼的 getter/setter,極少見但合法。public int Id { get; set; } // 編譯器自動產生隱私支援欄位
init 存取子(C# 9.0+)—— 物件初始化後不可變public class Person
{
public string Name { get; init; } // 僅能在物件初始化器中設定
}
var p = new Person { Name = "Tom" };
// p.Name = "Jerry"; // 編譯錯誤
required 修飾符(C# 11+)—— 強制初始化public class Config
{
public required string ConnectionString { get; init; }
}
var cfg = new Config { ConnectionString = "..." }; // 必填,否則編譯警告/錯誤
GetSomething())明確語意。readonly struct + 唯讀屬性,減少防禦性複製開銷。[MethodImpl(MethodImplOptions.AggressiveInlining)] 內聯優化。System.Text.Json 預設支援公用屬性;DataContractSerializer 需 [DataMember] 標記。欄位是資料的容器,屬性是資料的守門人。善用 readonly/const 表達不變性,透過屬性封裝驗證邏輯,配合 virtual/override/sealed 設計可擴充的繼承體系,並善用現代 C#(init、required、record)打造不可變資料模型,即能寫出健壯、可維護且效能優異的 C# 程式碼。