前言
類別是 C# 面向對象程式設計的基石。本文系統梳理類別宣告、成員分類、存取修飾詞、繼承、多型、建構子/解構子、屬性/索引器/事件/運算子等核心概念,並以實務設計原則(SOLID、封裝、組合優於繼承)穿插說明,幫助你寫出可維護、可擴充的程式碼。
一、類別宣告與基本結構
[attributes] [modifiers] class Identifier [: base-class, interfaces]
{
// 成員宣告
}
- modifiers:
public、internal、abstract、sealed、static、partial。
- base-class:單一繼承,預設為
object。
- interfaces:多重實作,以逗號分隔。
二、成員完整分類表
| 分類 | 成員類型 | 關鍵字/語法 | 說明 |
| 資料 | 常數 | const | 編譯期常值,隱含 static |
| 欄位 | field | 實例/靜態變數,建議 private + 屬性封裝 |
| 函式 | 方法 | method | 可多載、虛擬、覆寫、抽象 |
| 屬性 | property | get/set 存取子,語法糖封裝欄位 |
| 索引器 | this[] | 讓物件像陣列一樣存取 |
| 事件 | event | 發布/訂閱模式,基於委派 |
| 運算子 | operator | 多載 + - == != 等,必須 public static |
| 建構子 | ctor | 實例/靜態初始化,無回傳型別 |
| 解構子 | ~Class() | Finalizer,GC 呼叫,非決定性 |
| 型別 | nested type | 巢狀類別/結構/介面/委派 |
三、存取修飾詞與可見性
| 修飾詞 | 可見範圍 | 典型用途 |
| public | 無限制 | 公開 API |
| internal | 同一組件 | 內部實作細節 |
| protected | 衍生類別 | 給子類別擴充點 |
| protected internal | 同組件或衍生類別 | 跨組件擴充 |
| private | 僅含宣告類別 | 預設,封裝細節 |
| private protected | 同組件且衍生類別 | 極少用,嚴格封裝 |
原則:預設 private,需要時再放寬。欄位永遠 private readonly,對外暴露屬性。
四、繼承與多型
基礎語法
public class Point { public int X, Y; public Point(int x, int y) { X=x; Y=y; } }
public class Point3D : Point { public int Z; public Point3D(int x,int y,int z):base(x,y){ Z=z; } }
- 衍生類別隱式包含基底類別所有非建構子成員。
Point p = new Point3D(1,2,3); 隱含向上轉型。
virtual / override / abstract / sealed 控制多型行為。
抽象類別與介面選擇指南
| 特性 | abstract class | interface |
| 多重實作 | 單一繼承 | 多重實作 |
| 可包含實作 | 是 | C# 8+ default implementation |
| 可含狀態 | 是 | 不可 (僅 static 欄位) |
| 建構子 | 可有 | 不可 |
| 版本演進 | 破壞性較大 | 較易擴充 |
現代 C# 傾向:介面定義契約,抽象類別提供共用實作(如 abstract class RepositoryBase : IRepository)。
五、欄位、常數、唯讀
public class Color
{
public static readonly Color Red = new Color(255,0,0); // 靜態唯讀,執行期初始化
public const string ColorSpace = "sRGB"; // 編譯期常值
private readonly byte _r, _g, _b; // 實例唯讀,建構子賦值
public Color(byte r, byte g, byte b) { _r=r; _g=g; _b=b; }
}
const:隱含 static,值編譯期嵌入呼叫端 IL,修改需重新編譯所有參考組件。
readonly:執行期初始化,支援建構子邏輯,二進位相容性佳。
六、方法進階
參數傳遞模式
| 關鍵字 | 方向 | 說明 |
| (無) | In | 值傳遞 (參考型別傳址) |
| ref | In/Out | 呼叫端需初始化 |
| out | Out | 呼叫端無需初始化,方法必須指派 |
| in | In | 唯讀傳址,防止意外修改 (C# 7.2+) |
| params | In | 可變參數陣列,僅最後一個參數 |
方法多載解析優先序
- 確切匹配 (含隱含轉換)
- 隱含數值轉換 (int→long→float→double)
params 擴展
- 使用者定義隱含轉換
避免模稜兩可的多載(如 F(object) vs F(int)),必要時用強制轉型 F((object)1) 指定。
七、屬性、索引器、事件、運算子
屬性模式
// 傳統
private int _age;
public int Age { get => _age; set => _age = value >= 0 ? value : throw new ArgumentException(); }
// C# 6+ 自動實作 + 初始化
public int Age { get; set; } = 18;
// C# 9+ init-only (不可變物件)
public int Age { get; init; }
// 唯讀自動實作 (C# 6+)
public int Id { get; } = Guid.NewGuid().GetHashCode();
索引器
public class BitArray
{
private readonly uint[] _bits;
public bool this[int index]
{
get => (_bits[index >> 5] & (1u << (index & 31))) != 0;
set
{
if (value) _bits[index >> 5] |= 1u << (index & 31);
else _bits[index >> 5] &= ~(1u << (index & 31));
}
}
}
事件標準模式
public class Timer
{
public event EventHandler Elapsed;
protected virtual void OnElapsed(ElapsedEventArgs e) => Elapsed?.Invoke(this, e);
}
// 訂閱
timer.Elapsed += (s, e) => Console.WriteLine(e.SignalTime);
運算子多載
public struct Money
{
public decimal Amount { get; }
public string Currency { get; }
public static Money operator +(Money a, Money b)
{
if (a.Currency != b.Currency) throw new InvalidOperationException();
return new Money(a.Amount + b.Amount, a.Currency);
}
public static bool operator ==(Money a, Money b) => a.Amount == b.Amount && a.Currency == b.Currency;
public static bool operator !=(Money a, Money b) => !(a == b);
// 必須成對多載 == !=、< >、<= >=
}
八、建構子與解構子
實例建構子
- 可多載、鏈結呼叫
this(...)、呼叫基底 base(...)。
- 無參數建構子若未顯式宣告,編譯器產生預設公用無參建構子(結構除外)。
static 建構子:型別初始化器,執行緒安全、僅執行一次,適合靜態欄位複雜初始化。
解構子 / Finalizer
~MyClass() { /* 釋放非託管資源 */ }
// 等價於
protected override void Finalize() { try { /*...*/ } finally { base.Finalize(); } }
- 非決定性,由 GC 執行緒呼叫,順序不可預期。
- 實作
IDisposable + Dispose() 模式(含 GC.SuppressFinalize(this))為釋放資源正規做法。
九、實務設計原則速查
- 單一職責 (SRP):一個類別只負責一件事。
- 開放封閉 (OCP):對擴充開放,對修改關閉(介面 + 策略模式)。
- 里氏替換 (LSP):衍生類別必須能替換基底類別不破壞行為。
- 介面隔離 (ISP):介面要小而精,避免胖介面。
- 依賴反轉 (DIP):高層模組依賴抽象,不依賴具體實作 (DI 容器)。
- 組合優於繼承:
class Car { IEngine Engine; } 而非 class Car : Engine。
- 不可變性:
readonly、init、record (C# 9+) 減少狀態 Bug。
- 封裝欄位:永遠
private,對外用屬性/方法。
十、C# 9+ Record 型別:不可變資料的新選擇
// 位置語法
public record Person(string Name, int Age, Address Address);
// 等價於:不可變、value-based Equals/GetHashCode、with-expression、deconstruction
var p1 = new Person("Alice", 30, new Address("Taipei"));
var p2 = p1 with { Age = 31 }; // 複製並修改
Console.WriteLine(p1 == p2); // False (值比較)
資料傳輸物件 (DTO)、實體、值物件首選 record / record struct;行為豐富的領域模型仍用 class。
總結
精通類別與物件機制,不只是記語法,更要內化「封裝變化、依賴抽象、組合復用」的設計思維。善用 readonly/init/record 推進不可變風格,配合介面導向程式設計,即能構建經得起需求變更的高品質 C# 代碼庫。