5.2 泛型与泛型集合
概述
泛型(Generics)是C# 2.0引入的核心特性,它允许开发者编写可重用的、类型安全的代码,而无需为每种数据类型重复编写相同的逻辑。泛型集合则是基于泛型实现的强类型集合类(如List<T>、Dictionary<TKey, TValue>等),相比非泛型集合(如ArrayList),它们在性能和类型安全性上有显著优势。
泛型基础
1. 泛型类型定义
通过<T>语法声明泛型类型参数,T代表占位符类型(可替换为任意具体类型):
public class GenericBox<T>
{
public T Content { get; set; }
}
// 使用示例
var intBox = new GenericBox<int> { Content = 42 };
var stringBox = new GenericBox<string> { Content = "Hello" };
2. 泛型方法
方法也可以定义为泛型,独立于其所属类:
public T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
// 调用示例
int result = Max(10, 20); // 自动推断类型为int
3. 类型约束
通过where关键字限制泛型参数的类型范围:
where T : struct:必须是值类型where T : class:必须是引用类型where T : new():必须有无参构造函数where T : BaseClass:必须继承自指定基类where T : Interface:必须实现指定接口
泛型集合
1. 常用泛型集合类
| 集合类 | 描述 | 示例 |
|---|---|---|
List<T> | 动态数组 | var list = new List<int>(); |
Dictionary<TKey,TValue> | 键值对集合 | var dict = new Dictionary<string, int>(); |
HashSet<T> | 不重复元素的集合 | var set = new HashSet<string>(); |
Queue<T> | 先进先出队列 | var queue = new Queue<double>(); |
Stack<T> | 后进先出栈 | var stack = new Stack<bool>(); |
2. 与非泛型集合对比
// 非泛型ArrayList(不推荐)
ArrayList oldList = new ArrayList();
oldList.Add(1); // 装箱(性能损耗)
oldList.Add("text"); // 允许任意类型(类型不安全)
int num = (int)oldList[0]; // 需要显式拆箱
// 泛型List<T>(推荐)
List<int> newList = new List<int>();
newList.Add(1); // 无装箱
// newList.Add("text"); // 编译时报错
int num = newList[0]; // 直接访问
高级泛型特性
1. 协变与逆变(C# 4.0+)
- 协变(out T):允许使用派生程度更大的类型(如
IEnumerable<Cat>赋值给IEnumerable<Animal>) - 逆变(in T):允许使用派生程度更小的类型(如
Action<Animal>赋值给Action<Cat>)
// 协变示例
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // 合法,因为IEnumerable<out T>
// 逆变示例
Action<object> actObject = obj => Console.WriteLine(obj);
Action<string> actString = actObject; // 合法,因为Action<in T>
2. 默认值表达式
使用default关键字获取泛型类型的默认值:
T GetDefault<T>() => default(T);
Console.WriteLine(GetDefault<int>()); // 输出: 0
Console.WriteLine(GetDefault<string>()); // 输出: null
最佳实践
- 优先使用泛型集合:避免非泛型集合带来的装箱/拆箱开销和类型安全问题
- 合理使用约束:通过约束提高代码可读性并减少运行时错误
- 避免过度泛化:只在需要处理多种数据类型时使用泛型,否则会增加代码复杂度
示例项目:泛型缓存器
public class GenericCache<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _cache = new Dictionary<TKey, TValue>();
public void Add(TKey key, TValue value)
{
if (!_cache.ContainsKey(key))
{
_cache.Add(key, value);
}
}
public TValue Get(TKey key) => _cache.TryGetValue(key, out var value) ? value : default;
}
// 使用示例
var cache = new GenericCache<int, string>();
cache.Add(1, "Apple");
string result = cache.Get(1); // 返回"Apple"
