C# 泛型:从基础到高级应用

1. 什么是泛型?

泛型(Generics)是 C# 2.0 引入的核心特性,它允许在定义类、接口、方法时使用类型参数,从而创建可重用、类型安全且高性能的代码组件。简单来说,泛型让你可以编写一个能处理多种数据类型的代码模板,而无需为每种类型重复编写相似的代码。

在没有泛型之前,我们通常使用 object 类型来实现通用容器(如 ArrayList),但这会导致:

  • 类型不安全 :编译时无法检查类型,运行时可能抛出 InvalidCastException
  • 性能损失:值类型需要频繁装箱(boxing)和拆箱(unboxing)。
  • 代码冗余:为每种类型编写几乎相同的类或方法。

泛型完美解决了这些问题。

2. 泛型的基本语法

2.1 泛型类

在类名后使用尖括号 <T> 声明类型参数:

csharp 复制代码
// 定义一个简单的泛型容器类
public class GenericList<T>
{
    private T[] items;
    private int count = 0;
    
    public GenericList(int capacity)
    {
        items = new T[capacity];
    }
    
    public void Add(T item)
    {
        if (count < items.Length)
        {
            items[count] = item;
            count++;
        }
    }
    
    public T Get(int index)
    {
        if (index >= 0 && index < count)
            return items[index];
        throw new IndexOutOfRangeException();
    }
}

// 使用示例
GenericList<int> intList = new GenericList<int>(10);
intList.Add(42);
int value = intList.Get(0); // 类型安全,无需强制转换

GenericList<string> stringList = new GenericList<string>(5);
stringList.Add("Hello");
string text = stringList.Get(0);

2.2 泛型方法

方法也可以独立使用泛型,即使所在类不是泛型类:

csharp 复制代码
public class Utility
{
    // 泛型方法:交换两个变量的值
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    
    // 使用示例
    public static void Demo()
    {
        int x = 10, y = 20;
        Swap(ref x, ref y); // 编译器推断 T 为 int
        
        string s1 = "first", s2 = "second";
        Swap(ref s1, ref s2); // T 为 string
    }
}

2.3 泛型接口

接口同样可以定义类型参数:

csharp 复制代码
public interface IRepository<T>
{
    T GetById(int id);
    void Add(T entity);
    void Update(T entity);
    void Delete(int id);
}

// 实现泛型接口
public class UserRepository : IRepository<User>
{
    public User GetById(int id) { /* 实现 */ }
    public void Add(User entity) { /* 实现 */ }
    public void Update(User entity) { /* 实现 */ }
    public void Delete(int id) { /* 实现 */ }
}

3. 类型约束(Constraints)

默认情况下,泛型类型参数 T 可以是任何类型。但有时我们需要限制 T 必须满足某些条件,这时可以使用类型约束:

csharp 复制代码
// T 必须是引用类型(类、接口、委托、数组)
public class ReferenceTypeClass<T> where T : class
{
    public T Instance { get; set; }
}

// T 必须是值类型(结构体、枚举),且不能是可空类型
public struct ValueTypeStruct<T> where T : struct
{
    public T Value { get; set; }
}

// T 必须具有无参构造函数
public class Creatable<T> where T : new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

// T 必须继承自指定基类或实现指定接口
public class Processor<T> where T : IComparable, IFormattable
{
    public int Compare(T a, T b)
    {
        return a.CompareTo(b);
    }
}

// 多重约束
public class ComplexGeneric<T> where T : class, IComparable<T>, new()
{
    // T 必须是引用类型、实现 IComparable<T>、且有默认构造函数
}

4. 泛型集合(System.Collections.Generic)

.NET Framework 提供了丰富的内置泛型集合类,这是泛型最常用的场景:

csharp 复制代码
using System.Collections.Generic;

// List<T>:动态数组
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Add(6);

// Dictionary<TKey, TValue>:键值对集合
Dictionary<string, int> ages = new Dictionary<string, int>
{
    ["Alice"] = 25,
    ["Bob"] = 30
};
int bobAge = ages["Bob"];

// Queue<T>:先进先出队列
Queue<string> queue = new Queue<string>();
queue.Enqueue("first");
queue.Enqueue("second");
string firstItem = queue.Dequeue();

// Stack<T>:后进先出栈
Stack<double> stack = new Stack<double>();
stack.Push(3.14);
stack.Push(2.71);
double top = stack.Pop();

// HashSet<T>:不包含重复元素的集合
HashSet<string> uniqueNames = new HashSet<string>
{
    "John", "Jane", "John" // 第二个 "John" 不会被添加
};

5. 协变与逆变(Covariance & Contravariance)

C# 4.0 引入了泛型接口和委托的协变与逆变,提供了更灵活的类型转换:

5.1 协变(out 关键字)

允许使用派生程度更大的类型(更具体的类型)。只能用于输出位置(返回值):

csharp 复制代码
// IEnumerable<T> 是协变的:IEnumerable<out T>
IEnumerable<string> strings = new List<string> { "a", "b", "c" };
// 可以将 IEnumerable<string> 赋值给 IEnumerable<object>
IEnumerable<object> objects = strings; // 协变,因为 string 派生自 object

// 自定义协变接口
public interface IProducer<out T>
{
    T Produce();
}

public class StringProducer : IProducer<string>
{
    public string Produce() => "Hello";
}

IProducer<string> stringProducer = new StringProducer();
IProducer<object> objectProducer = stringProducer; // 协变允许

5.2 逆变(in 关键字)

允许使用派生程度更小的类型(更通用的类型)。只能用于输入位置(参数):

csharp 复制代码
// IComparer<T> 是逆变的:IComparer<in T>
IComparer<object> objectComparer = Comparer<object>.Default;
// 可以将 IComparer<object> 赋值给 IComparer<string>
IComparer<string> stringComparer = objectComparer; // 逆变,因为 object 是 string 的基类

// 自定义逆变接口
public interface IConsumer<in T>
{
    void Consume(T item);
}

public class ObjectConsumer : IConsumer<object>
{
    public void Consume(object item) { }
}

IConsumer<object> objectConsumer = new ObjectConsumer();
IConsumer<string> stringConsumer = objectConsumer; // 逆变允许

6. 泛型委托

委托也可以使用泛型,.NET 内置了常用的泛型委托:

csharp 复制代码
// Func<TResult>:无参数,返回 TResult
Func<int> getRandom = () => new Random().Next(100);
int randomValue = getRandom();

// Func<T, TResult>:一个参数,返回 TResult
Func<string, int> stringLength = s => s.Length;
int length = stringLength("Hello");

// Action<T>:一个参数,无返回值
Action<string> logger = message => Console.WriteLine(message);
logger("Log message");

// Predicate<T>:一个参数,返回 bool
Predicate<int> isEven = n => n % 2 == 0;
bool result = isEven(4);

// 自定义泛型委托
public delegate T Transformer<T>(T input);
public delegate TResult Converter<TInput, TResult>(TInput input);

Transformer<int> square = x => x * x;
int squared = square(5); // 25

7. 泛型的最佳实践

  • 优先使用泛型集合 :总是使用 List<T>Dictionary<TKey, TValue> 等泛型集合,避免使用 ArrayListHashtable 等非泛型集合。
  • 合理使用约束:只在必要时添加约束,过多的约束会限制泛型的灵活性。
  • 考虑可空性 :C# 8.0 的可空引用类型与泛型结合时,使用 T? 语法或 default 关键字处理可能为 null 的情况。
  • 避免过度泛化:如果类型参数只在少数地方使用,考虑使用重载方法而非泛型。
  • 性能考量:泛型在编译时生成特定类型的代码,避免了装箱拆箱,性能接近硬编码类型。

8. 总结

C# 泛型是现代 .NET 开发的基石,它提供了:

  1. 类型安全:编译时类型检查,减少运行时错误。
  2. 代码重用:一套代码处理多种数据类型。
  3. 性能优化:避免装箱拆箱,生成高效的特化代码。
  4. 丰富的生态系统:LINQ、集合类、异步编程等都深度依赖泛型。

掌握泛型不仅能让你的代码更加健壮和高效,也是理解 .NET 高级特性(如 LINQ、异步流、依赖注入容器)的必要前提。

相关推荐
ly76891 小时前
Java 设计模式详解:从原则到 23 种经典模式
java·开发语言·设计模式
SomeB1oody1 小时前
【RustyML入门】2.13. 孤立森林
开发语言·后端·机器学习·rust·教程
Vertira1 小时前
C# 与 OpencvSharp 编程[基础目录]
c#
萧瑟余晖1 小时前
Java深入解析篇三十之Java日志框架
java·开发语言
Herbert_hwt1 小时前
【C语言基础】常量、选择结构与运算符全解析
c语言·开发语言·算法
AI视觉网奇2 小时前
动作识别 视频理解大模型
开发语言·python·音视频
SomeB1oody2 小时前
【RustyML入门】3.8. 正则化与归一化层
开发语言·后端·机器学习·rust·教程
__zRainy__2 小时前
Node系列 · Node基础:全局变量与全局对象
开发语言·前端·javascript
鬼手点金3 小时前
Scrapy + Playwright 完整示例(JS 动态渲染网页)
开发语言·javascript·爬虫·python·scrapy·html·json