泛型是 C# 2.0 引入的核心特性,它允许在定义类、接口、方法、委托等时使用未指定的类型参数,在使用时再指定具体类型。这种机制可以显著提高代码的复用性、类型安全性和性能。
一、泛型的核心概念
类型参数化
泛型允许将类型作为 "参数" 传递给类、方法等,就像方法可以接受数值参数一样。例如,List<T>
中的T
就是类型参数,使用时可以指定为List<int>
、List<string>
等。编译时类型检查
泛型在编译阶段就会进行类型验证,避免了运行时的类型转换错误。例如,List<int>
只能存储int
类型,编译器会阻止添加字符串等其他类型。消除装箱 / 拆箱操作
对于值类型(如int
、double
),非泛型集合(如ArrayList
)会将值类型装箱为object
,取出时再拆箱,造成性能损耗。泛型集合(如List<int>
)直接存储值类型,避免了这一过程。代码复用
一套泛型代码可以适配多种数据类型,无需为每种类型重复编写逻辑(如排序、查找等)。
二、泛型的基本使用
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> ) |
|
|
T 必须是引用类型 |
|
where T : new() | T 必须有公共无参构造函数 |
|
where T : 基类名 | 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
命名空间),替代了非泛型集合(如 ArrayList
、Hashtable
):
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]
}
四、泛型的优势总结
- 类型安全:编译时检查类型,避免运行时类型转换错误。
- 性能优化:减少装箱 / 拆箱操作,尤其对值类型更高效。
- 代码复用:一套逻辑适配多种类型,减少重复代码。
- 灵活性:结合约束、协变、逆变等特性,可适应复杂场景。
五、泛型的适用场景
- 集合类(如自定义列表、字典)
- 工具类(如转换器、比较器)
- 数据访问层(如通用仓储模式)
- 委托和事件(如通用回调函数)
- 算法实现(如排序、搜索,适用于多种数据类型)
通过泛型,C# 代码可以在保持类型安全的同时实现高度复用。