CodeGym /課程 /C# SELF /.NET 標準函式庫裡的介面

.NET 標準函式庫裡的介面

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

1. 主要介面的分類

如果你覺得介面只是架構師或「乾淨程式碼」的玩具,實際寫專案根本用不到,那我要讓你驚訝一下:任何認真的 C# 專案都會用爆介面。為什麼?因為 .NET 標準函式庫幾乎每個部分都靠介面在運作!沒有它們你沒辦法玩集合、讀檔案,甚至用 LINQ 過濾集合都不行。

介面就是 .NET 多型跟擴充性的基礎,它們決定了 framework 裡所有「積木」怎麼拼在一起。

.NET 標準函式庫根本塞滿介面。為了不被淹沒,我建議這樣分類(當然不可能全包啦,人生沒那麼長!):

分類 介面 用途?
集合
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
遍歷、修改、用索引存取、key-value 配對操作
資源操作
IDisposable
釋放資源(檔案、連線、stream)
比較
IComparable
,
IComparer
,
IEquatable
排序、比較、物件唯一性
序列化
ISerializable
把物件變成 byte stream(或反過來)
LINQ 跟查詢
IQueryable
,
IQueryProvider
支援複雜查詢(像資料庫那種)
非同步
IAsyncEnumerable
,
IAsyncDisposable
非同步遍歷跟資源清理
事件與通知
INotifyPropertyChanged
,
INotifyCollectionChanged
對屬性/集合變動做反應
日期與時間
IFormattable
自訂字串格式化
資料結構
IStructuralComparable
,
IStructuralEquatable
深度比較集合跟 tuple
Stream/輸入輸出
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
處理 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();

更多細節看 Microsoft 文件。

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),不然編譯器會不爽你。

2
任務
C# SELF, 等級 24, 課堂 3
上鎖
檢查 IEnumerable 介面
檢查 IEnumerable 介面
2
任務
C# SELF, 等級 24, 課堂 3
上鎖
使用 IComparable 介面進行排序
使用 IComparable 介面進行排序
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION