在 C# 開發中,實體類別幾乎無處不在。定義資料成員時,我們面臨三種選擇:常規屬性、自動實作屬性、欄位。本文將深入剖析三者的差異、適用場景與最佳實踐,助你做出正確的架構決策。
一個完整的常規屬性包含三個部分:
private 封裝,強制透過屬性存取。
private string _name;
return 或 throw 結束。
get
{
// 價格不可為負,異常時回傳 0
return _price >= 0 ? _price : 0;
}
原則:get 不應修改物件狀態(如回傳時附加字串屬不佳實踐)。
void 方法,可包含邏輯處理(如預設值計算)。
set
{
_name = value?.Trim() ?? string.Empty;
}
| 維度 | 說明 |
|---|---|
| 存取修飾詞 | public、private、protected、internal、protected internal、private protected |
| 靜態性 | 靜態屬性、實例屬性(靜態屬性不可與 virtual/abstract/override 混用) |
| 虛擬/抽象 | 虛屬性、抽象屬性、覆寫屬性 |
| 讀寫性 | 唯讀、唯寫、可讀可寫 |
當存取子不需要額外邏輯時,可使用自動實作屬性:
public string Name { get; set; }
編譯器會自動產生私有匿名後備欄位,僅能透過屬性存取。
private set。| 特性 | 欄位 | 屬性 |
|---|---|---|
| 資料驗證 | 無法攔截賦值(如身高設 1000m) | 可在 set 中攔截驗證 |
| ref/out 傳遞 | 支援 | 不支援(屬性非變數分類) |
| 序列化/繫結 | 部分需特殊處理 | 標準支援 |
| 介面定義 | 不可用於介面 | 可定義於介面 |
class Student { public string Name { get; set; } }
void RefMethod(ref string name) { name += "ref"; }
void OutMethod(string name, out string name2) { name2 = name + "out"; }
var s = new Student { Name = "王五" };
RefMethod(ref s.Name); // 編譯錯誤 CS0206
OutMethod(s.Name, out var n); // 編譯錯誤 CS0206
private 配合方法封裝)。常規屬性支援反射列舉,適合物件間屬性批次複製:
PropertyInfo[] srcProps = src.GetType().GetProperties();
PropertyInfo[] dstProps = dst.GetType().GetProperties();
foreach (var sp in srcProps)
{
if (!sp.CanRead) continue;
var dp = dstProps.FirstOrDefault(p =>
p.Name.Equals(sp.Name, StringComparison.OrdinalIgnoreCase) &&
p.PropertyType == sp.PropertyType &&
p.CanWrite);
if (dp != null)
dp.SetValue(dst, sp.GetValue(src));
}
此技巧廣泛用於 DTO 轉換、領域模型映射等場景。
屬性是封裝的第一道防線。自動實作屬性兼顧簡潔與擴充性,是現代 C# 開發的預設選擇;常規屬性處理複雜業務規則;欄位僅限內部實作細節。善用三者,寫出健壯且可維護的程式碼。