C# 泛型(Generics)详解

发布于:2025-08-14 ⋅ 阅读:(15) ⋅ 点赞:(0)

泛型是 C# 2.0 引入的核心特性,它允许在定义类、接口、方法、委托等时使用未指定的类型参数,在使用时再指定具体类型。这种机制可以显著提高代码的复用性、类型安全性和性能。

一、泛型的核心概念
  1. 类型参数化
    泛型允许将类型作为 "参数" 传递给类、方法等,就像方法可以接受数值参数一样。例如,List<T> 中的 T 就是类型参数,使用时可以指定为 List<int>List<string> 等。

  2. 编译时类型检查
    泛型在编译阶段就会进行类型验证,避免了运行时的类型转换错误。例如,List<int> 只能存储 int 类型,编译器会阻止添加字符串等其他类型。

  3. 消除装箱 / 拆箱操作
    对于值类型(如 intdouble),非泛型集合(如 ArrayList)会将值类型装箱为 object,取出时再拆箱,造成性能损耗。泛型集合(如 List<int>)直接存储值类型,避免了这一过程。

  4. 代码复用
    一套泛型代码可以适配多种数据类型,无需为每种类型重复编写逻辑(如排序、查找等)。

二、泛型的基本使用
1. 泛型类(Generic Classes)

泛型类是最常用的泛型形式,定义时在类名后添加 <类型参数>,使用时指定具体类型。

// 定义泛型类
public class MyGenericClass<T>
{
    private T _value;
    
    public MyGenericClass(T value)
    {
        _value = value;
    }
    
    public T GetValue()
    {
        return _value;
    }
    
    public void SetValue(T value)
    {
        _value = value;
    }
}

// 使用泛型类
var intContainer = new MyGenericClass<int>(10);
int intValue = intContainer.GetValue(); // 10

var stringContainer = new MyGenericClass<string>("Hello");
string stringValue = stringContainer.GetValue(); // "Hello"
2. 泛型方法(Generic Methods)

泛型方法可以在普通类或泛型类中定义,方法名后添加 <类型参数>,调用时可显式或隐式指定类型。

public class GenericMethodExample
{
    // 定义泛型方法
    public T Max<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) > 0 ? a : b;
    }
}

// 使用泛型方法
var example = new GenericMethodExample();
int maxInt = example.Max(5, 10); // 10(隐式推断类型为int)
string maxStr = example.Max<string>("apple", "banana"); // "banana"(显式指定类型)
3. 泛型接口(Generic Interfaces)

泛型接口与泛型类类似,常用于定义集合、比较器等具有通用性的契约。 

// 定义泛型接口
public interface IRepository<T>
{
    T GetById(int id);
    void Add(T item);
    void Update(T item);
    void Delete(int id);
}

// 实现泛型接口(以用户仓储为例)
public class UserRepository : IRepository<User>
{
    public User GetById(int id) { /* 实现 */ }
    public void Add(User item) { /* 实现 */ }
    public void Update(User item) { /* 实现 */ }
    public void Delete(int id) { /* 实现 */ }
}
4. 泛型委托(Generic Delegates)

泛型委托允许定义可接受不同类型参数的委托,C# 内置的 Func<T>Action<T> 就是典型例子。

// 定义泛型委托
public delegate T Transformer<T>(T input);

// 使用泛型委托
public class DelegateExample
{
    public static int Square(int x) => x * x;
    public static string ToUpper(string s) => s.ToUpper();
}

// 调用
Transformer<int> intTransformer = DelegateExample.Square;
int result = intTransformer(5); // 25

Transformer<string> stringTransformer = DelegateExample.ToUpper;
string upperStr = stringTransformer("hello"); // "HELLO"
5. 泛型约束(Constraints)

泛型约束用于限制类型参数的范围,确保类型参数满足特定条件(如必须实现某接口、必须是引用类型等)。常用约束如下:

约束语法 说明
where T : struct T 必须是值类型(非 Nullable<T>
where T : class
T 必须是引用类型
where T : new() T 必须有公共无参构造函数
where T : 基类名 T 必须是指定基类或其派生类
where T : 接口名
T 必须实现指定接口
where T : U T 必须是 U 或其派生类(用于多参数)

示例:

// 约束T必须实现IComparable<T>接口
public class GenericWithConstraint<T> where T : IComparable<T>
{
    public T FindMax(T[] items)
    {
        if (items == null || items.Length == 0)
            throw new ArgumentException("数组不能为空");
        
        T max = items[0];
        foreach (var item in items)
        {
            if (item.CompareTo(max) > 0)
                max = item;
        }
        return max;
    }
}
6. 泛型集合(Generic Collections)

.NET Framework 提供了丰富的泛型集合类(位于 System.Collections.Generic 命名空间),替代了非泛型集合(如 ArrayListHashtable):

  • List<T>:动态数组,替代 ArrayList
  • Dictionary<TKey, TValue>:键值对集合,替代 Hashtable
  • HashSet<T>:无序唯一元素集合
  • Queue<T>:先进先出(FIFO)队列
  • Stack<T>:后进先出(LIFO)栈

示例:

using System.Collections.Generic;

// 使用List<T>
var numbers = new List<int> { 1, 2, 3 };
numbers.Add(4);
int first = numbers[0];

// 使用Dictionary<TKey, TValue>
var personAges = new Dictionary<string, int>
{
    { "Alice", 30 },
    { "Bob", 25 }
};
int aliceAge = personAges["Alice"];
三、泛型的高级特性
1. 泛型类型参数的协变与逆变
  • 协变(Covariance):允许将泛型类型参数从派生类隐式转换为基类,使用 out 关键字标记(仅适用于接口和委托)。
  • 逆变(Contravariance):允许将泛型类型参数从基类隐式转换为派生类,使用 in 关键字标记(仅适用于接口和委托)。

示例:

// 协变接口(out关键字)
public interface IEnumerable<out T> { ... }

// 逆变接口(in关键字)
public interface IComparer<in T> { ... }

// 用法
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // 协变:string → object

IComparer<object> objectComparer = new ObjectComparer();
IComparer<string> stringComparer = objectComparer; // 逆变:object → string
2. 静态字段与泛型

泛型类的静态字段在不同封闭类型(如 MyClass<int> 和 MyClass<string>)中是独立的,不会共享:

public class StaticGeneric<T>
{
    public static int Count { get; set; } = 0;
}

// 不同类型的静态字段独立
StaticGeneric<int>.Count = 1;
StaticGeneric<string>.Count = 2;
Console.WriteLine(StaticGeneric<int>.Count); // 1(与string的Count无关)

List<int> 本质上是泛型类 List<T> 的一个实例化版本,它依赖于泛型机制才能存在。如果没有 List<T> 这个泛型定义,就无法通过传入 int 得到 List<int>

T 是泛型的 "模板参数",而 int 是填充这个模板的 "实际参数"。List<int> 是泛型机制的产物,因此它是泛型集合的典型应用。

3. 泛型类型的反射

可以通过反射获取泛型类型的信息,如类型参数、约束等:

Type listType = typeof(List<int>);
if (listType.IsGenericType)
{
    Type genericTypeDefinition = listType.GetGenericTypeDefinition(); // 得到List<T>
    Type[] typeArguments = listType.GetGenericArguments(); // 得到[int]
}
四、泛型的优势总结
  1. 类型安全:编译时检查类型,避免运行时类型转换错误。
  2. 性能优化:减少装箱 / 拆箱操作,尤其对值类型更高效。
  3. 代码复用:一套逻辑适配多种类型,减少重复代码。
  4. 灵活性:结合约束、协变、逆变等特性,可适应复杂场景。
五、泛型的适用场景
  • 集合类(如自定义列表、字典)
  • 工具类(如转换器、比较器)
  • 数据访问层(如通用仓储模式)
  • 委托和事件(如通用回调函数)
  • 算法实现(如排序、搜索,适用于多种数据类型)

通过泛型,C# 代码可以在保持类型安全的同时实现高度复用。


网站公告

今日签到

点亮在社区的每一天
去签到