首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 泛型方法入門指南:型別參數、推斷、約束與多載實戰

     編輯:關於C語言

前言

泛型方法讓您能編寫「型別參數化」的演算法,在編譯期保持型別安全,執行期避免裝箱/拆箱與轉型開銷。本文從基本語法出發,涵蓋型別推斷、類別層級型別參數存取、約束條件、多載規則等核心概念,並附上實用程式碼範例。

一、基本語法與呼叫

宣告泛型方法

在方法名稱後加上 <T> 宣告型別參數:

static void Swap<T>(ref T lhs, ref T rhs)
{
    T temp = lhs;
    lhs = rhs;
    rhs = temp;
}

明確指定型別引數呼叫

public static void TestSwap()
{
    int a = 1, b = 2;
    Swap<int>(ref a, ref b);  // 明確指定 T 為 int
    Console.WriteLine($"{a} {b}");  // 輸出: 2 1
}

型別推斷

編譯器可根據實際引數推斷型別參數,允許省略 <int>:

Swap(ref a, ref b);  // 編譯器推斷 T 為 int

推斷規則:

  • 依據方法參數型別推斷,不依據約束條件或傳回型別。
  • 無參數方法無法推斷,必須明確指定。
  • 推斷發生於編譯期、多載解析之前。

二、在泛型類別中存取類別層級型別參數

非泛型方法使用類別的型別參數

class SampleClass<T>
{
    // 非泛型方法,直接使用類別的 T
    void Swap(ref T lhs, ref T rhs) { /* ... */ }
}

避免型別參數隱藏警告 (CS0693)

若方法自行宣告同名型別參數,會隱藏外層類別的同名參數,編譯器發出警告:

class GenericList<T>
{
    // 警告 CS0693: 方法的 T 隱藏了類別的 T
    void SampleMethod<T>() { }  
}

class GenericList2<T>
{
    // 改用不同名稱 U,無警告
    void SampleMethod<U>() { }  
}

三、型別參數約束

where 子句限制型別參數必須滿足特定條件,啟用更專業操作(如介面方法呼叫、比較、建構函式呼叫等)。

範例:僅接受實作 IComparable<T> 的型別

void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : IComparable<T>
{
    if (lhs.CompareTo(rhs) > 0)
    {
        T temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}

現在呼叫端傳入的型別必須實作 IComparable<T>,否則編譯錯誤。常用約束關鍵字:

約束說明
where T : class參考型別(類別、介面、委派等)
where T : struct實值型別(排除 Nullable)
where T : new()有無參數建構函式
where T : BaseClass繼承自指定基底類別
where T : Interface實作指定介面
where T : notnull不可為 null 的型別 (C# 8.0+)
where T : unmanaged非受控型別 (C# 7.3+)
where T : default覆寫繼承而來的約束 (C# 9.0+)

可組合多個約束:where T : class, IComparable<T>, new()

四、泛型方法多載

泛型方法可與非泛型方法、其他泛型方法共存構成多載群:

void DoWork() { }              // 非泛型
void DoWork<T>() { }           // 1 個型別參數
void DoWork<T, U>() { }        // 2 個型別參數

多載解析優先序:

  1. 非泛型方法優先(若簽章完全匹配)。
  2. 型別參數較少者優先(在推斷成功前提下)。
  3. 具體型別優於泛型(如 DoWork(int) 勝過 DoWork<T>(T))。

五、進階範例:泛型擴充方法

public static class EnumerableExtensions
{
    // 擴充方法 + 泛型 + 約束
    public static T MaxBy<T, TKey>(this IEnumerable<T> source, 
                                    Func<T, TKey> selector) 
        where TKey : IComparable<TKey>
    {
        using var enumerator = source.GetEnumerator();
        if (!enumerator.MoveNext()) 
            throw new InvalidOperationException("序列為空");
        
        T maxItem = enumerator.Current;
        TKey maxKey = selector(maxItem);
        
        while (enumerator.MoveNext())
        {
            TKey currentKey = selector(enumerator.Current);
            if (currentKey.CompareTo(maxKey) > 0)
            {
                maxKey = currentKey;
                maxItem = enumerator.Current;
            }
        }
        return maxItem;
    }
}

// 使用範例
var people = new[] 
{
    new { Name = "Alice", Age = 30 },
    new { Name = "Bob", Age = 25 },
    new { Name = "Charlie", Age = 35 }
};
var oldest = people.MaxBy(p => p.Age);  // 型別推斷:T=匿名型別, TKey=int
Console.WriteLine($"最年長: {oldest.Name} ({oldest.Age} 歲)");

六、最佳實務總結

  • 命名慣例:單一型別參數用 T、TKey、TValue 等具體名稱;多個參數用 T1、T2 或語意名稱。
  • 盡量加上約束:讓編譯器盡早攔截錯誤,IntelliSense 提示更精確。
  • 避免過度泛型化:若方法內部不依賴型別特定行為,考慮用 object 或非泛型版本。
  • 靜態泛型類別:static class Cache<T> 可實現型別專屬快取。
  • 預設值:使用 default(T) 或 default (C# 7.1+) 取得型別預設值。

結語

泛型方法是 C# 型別系統威力的核心展現。掌握型別推斷、約束、多載解析規則,能讓您撰寫出既彈性又安全、效能優異的可重用程式碼。從簡單的 Swap<T> 到 LINQ 風格的擴充方法,泛型無處不在,值得深入鑽研。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved