1. 主要介面的分類
如果你覺得介面只是架構師或「乾淨程式碼」的玩具,實際寫專案根本用不到,那我要讓你驚訝一下:任何認真的 C# 專案都會用爆介面。為什麼?因為 .NET 標準函式庫幾乎每個部分都靠介面在運作!沒有它們你沒辦法玩集合、讀檔案,甚至用 LINQ 過濾集合都不行。
介面就是 .NET 多型跟擴充性的基礎,它們決定了 framework 裡所有「積木」怎麼拼在一起。
.NET 標準函式庫根本塞滿介面。為了不被淹沒,我建議這樣分類(當然不可能全包啦,人生沒那麼長!):
| 分類 | 介面 | 用途? |
|---|---|---|
| 集合 | , , , , |
遍歷、修改、用索引存取、key-value 配對操作 |
| 資源操作 | |
釋放資源(檔案、連線、stream) |
| 比較 | , , |
排序、比較、物件唯一性 |
| 序列化 | |
把物件變成 byte stream(或反過來) |
| LINQ 跟查詢 | , |
支援複雜查詢(像資料庫那種) |
| 非同步 | , |
非同步遍歷跟資源清理 |
| 事件與通知 | , |
對屬性/集合變動做反應 |
| 日期與時間 | |
自訂字串格式化 |
| 資料結構 | , |
深度比較集合跟 tuple |
| Stream/輸入輸出 | , , , |
處理 stream、push/pull 通知 |
注意! 上面很多介面都有型別參數:List<string>。通常用在集合跟集合介面 Int[] → List<int>。細節我們之後學集合時會再聊。
2. 集合的介面
IEnumerable 跟 IEnumerator — 你的 foreach 通行證
幾乎所有 .NET 集合都會實作 IEnumerable 或它的泛型版 IEnumerable<T>。這個介面才讓你能用神奇的 foreach 語法:
List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // 會動是因為 List<int> 有實作 IEnumerable<int>
{
Console.WriteLine(n);
}
這個介面最精簡長這樣:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator 就是那個「遍歷器」,負責走訪你的集合:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
實務上你超常會傳參數或回傳 IEnumerable<T>。像這樣,回傳陣列裡所有偶數的函式:
public IEnumerable<int> GetEvenNumbers(int[] array)
{
foreach (var x in array)
if (x % 2 == 0) yield return x;
}
對,yield 關鍵字會自動幫你搞定介面,之後會細講。
ICollection 跟 IList — 可以用索引跟改內容的集合
如果你不只要遍歷,還想加/刪元素,甚至用索引拿東西,就要用更專業的介面:
- ICollection<T> — 多了 Add、Remove 方法,還有 Count 屬性。
- IList<T> — 再多支援索引存取(this[int index])。
public void PrintFirstItem(IList<string> list)
{
if (list.Count > 0)
Console.WriteLine(list[0]);
}
List<T> 同時實作 IEnumerable<T>、ICollection<T>、IList<T>。超方便 — 你可以當它是簡單可遍歷清單,也能用所有進階功能。
IDictionary<TKey, TValue> — 「key-value」配對
你喜歡用 dictionary(超常用!)就用 IDictionary<TKey, TValue>。它保證你可以用 key 拿 value,反之亦然。
public void PrintAllPairs(IDictionary<string, int> ages)
{
foreach (var pair in ages)
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
這裡 ages 可以是 Dictionary<string, int>,也可以是 SortedDictionary<string, int> — 只要有實作這個介面就行!
3. IDisposable 介面:正確處理資源
所有輸入輸出、檔案、網路連線、資料庫在 .NET 都靠 IDisposable。這個介面定義一個超重要契約:如果物件有 unmanaged 資源,用完就要「清掉」。對,就是讓 using 語法成立的那傢伙:
using (StreamReader reader = new StreamReader("file.txt"))
{
// 處理檔案,using 結束後檔案一定會被關掉!
string line = reader.ReadLine();
}
介面本身超簡單:
public interface IDisposable
{
void Dispose();
}
但所有「認真」的函式庫都會實作它!更多好習慣請看官方文件。
4. 比較與唯一性的介面
IComparable<T> 跟 IComparer<T>
你想讓你的物件集合可以排序,物件就要能互相比較。這就是 IComparable<T> 的用途:
public class Student : IComparable<Student>
{
public string Name { get; set; }
public int Score { get; set; }
public int CompareTo(Student? other)
{
// 依分數遞減排序
if (other == null) return 1;
return other.Score.CompareTo(this.Score);
}
}
// 現在可以排序學生了:
var students = new List<Student> { ... };
students.Sort();
IComparer<T> 讓你在 class 外定義比較方式 — 比如有時想用名字排,有時用分數:
public class NameComparer : IComparer<Student>
{
public int Compare(Student? x, Student? y)
{
return string.Compare(x?.Name, y?.Name);
}
}
IEquatable<T> — 判斷相等
想讓你的物件能進 HashSet<T>(也就是對集合來說「唯一」)?就要實作 IEquatable<T>:
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public bool Equals(Person? other)
{
if (other == null) return false;
return this.Name == other.Name;
}
}
5. 事件與通知的介面
你有沒有想過讓程式「知道」物件屬性變了?比如 UI 自動更新,當使用者改姓氏?這就是 INotifyPropertyChanged 存在的原因。它在 GUI 應用(像 WPF、Xamarin)超紅。
介面簽名很簡單:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler? PropertyChanged;
}
實作範例:
public class User : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string name;
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
}
現在 UI 綁定這個物件的話,Name 變了就會自動更新。有興趣可以看官方文件。
6. 其他實用介面
IFormattable 介面:彈性格式化
想讓你的物件能漂亮又多樣地轉成字串(比如小數點幾位),就實作 IFormattable:
public class Temperature : IFormattable
{
public double Celsius { get; }
public Temperature(double celsius) => Celsius = celsius;
public string ToString(string? format, IFormatProvider? formatProvider)
{
// 不複雜,根據格式顯示 'C' 或 'F'
if (format == "F")
return $"{Celsius * 9 / 5 + 32} F";
return $"{Celsius} C";
}
}
現在你可以呼叫 temp.ToString("F", null) 或 temp.ToString("C", null)。這也是為什麼 DateTime、double、decimal 等內建型別都能彈性格式化。
非同步用的介面
C# 有了 async 之後,非同步世界也需要新介面。比如你的物件要 async 釋放資源,就實作 IAsyncDisposable:
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
現在你可以用 await using 取代 using!
非同步列舉(await foreach)用 IAsyncEnumerable<T>,可以不阻塞主執行緒地遍歷元素。像讀大檔案分段、網路資料流都會用到。細節等到 58 關再說 :P
序列化介面:ISerializable
你要把物件變 byte stream 存檔或傳輸(像網路),.NET 給你 ISerializable。現在比較少用,因為有更方便的(像 attribute 跟現成 serializer),但還是值得一提。介面長這樣:
public interface ISerializable
{
void GetObjectData(SerializationInfo info, StreamingContext context);
}
7. 介面實戰:真實應用
假設你要寫一個圖書館書籍管理的 console app(總要有人做吧!)。你想要能列出書、排序、過濾、載入跟存檔。靠 .NET 介面你可以:
- 用 IEnumerable<Book> 回傳各種集合型別,讓使用者不用管是陣列還是清單。
- 加多種排序方式:實作 IComparable<Book> 讓書名排序,另外寫 IComparer<Book> 讓作者排序。
- 檔案操作時有效釋放資源,靠 IDisposable。
- 用 LINQ 過濾書籍(依類型或作者),因為 LINQ 支援所有實作 IEnumerable<T> 的東西。
- 讓 app 可擴充 — 比如把檔案儲存換成資料庫,只要 class 都用介面(IBookStorage)而不是直接實作就行。
8. 常見錯誤與注意事項
新手最常犯的錯,就是用具體實作(「List」)而不是介面(「IEnumerable」、「IList」)來宣告變數或方法參數。記住:「用介面寫程式」你的 code 才會彈性又好擴充。
有時要小心選對介面層級 — 例如你只需要遍歷功能,參數就用 IEnumerable<T>,不要用 IList<T>,這樣才不會多加限制。
還有一個小細節:如果 class 實作很多介面,裡面有重複的方法或屬性,得用「明確介面實作」(explicit implementation),不然編譯器會不爽你。
GO TO FULL VERSION