01-泛型(Generics)
泛型允许你定义类型安全的类、接口和方法,而将具体类型延迟到使用时再指定。泛型提高了代码的复用性、类型安全性和性能。
一、为什么需要泛型
非泛型的问题
csharp
// 问题1:需要为每种类型编写重复代码
int CompareInt(int a, int b) { return a.CompareTo(b); }
double CompareDouble(double a, double b) { return a.CompareTo(b); }
string CompareString(string a, string b) { return a.CompareTo(b); }
// 问题2:使用 object 类型有装箱拆箱和类型安全问题
ArrayList list = new ArrayList();
list.Add(123); // 装箱:int → object
list.Add("string"); // 无类型安全(可以混入不同类型)
int val = (int)list[0]; // 拆箱:object → int,需要强制转换
// 泛型解决方案:一个方法适用所有类型
T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}泛型 vs 非泛型对比
| 对比项 | 非泛型(object) | 泛型 |
|---|---|---|
| 类型安全 | 无(可混入不同类型) | 有(编译时检查) |
| 值类型性能 | 装箱拆箱,性能差 | 直接操作,无额外开销 |
| 代码复用 | 需为每种类型写重复代码 | 一份代码适用所有类型 |
| 可读性 | 需强制转换,代码冗余 | 无需转换,代码清晰 |
| IDE 支持 | 无智能提示 | 有完整智能提示 |
二、泛型方法
基本语法
csharp
// 定义泛型方法
public static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
// 使用——编译器自动推断类型
int intMax = Max(10, 20); // T = int
double dblMax = Max(3.14, 2.71); // T = double
string strMax = Max("apple", "banana"); // T = string泛型方法交换值
csharp
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}"); // 输出:2, 1
string s1 = "Hello", s2 = "World";
Swap(ref s1, ref s2);
Console.WriteLine($"{s1}, {s2}"); // 输出:World, Hello类型推断规则
csharp
public static void Print<T>(T item)
{
Console.WriteLine(item);
}
Print(123); // T = int(自动推断)
Print("Hello"); // T = string
Print(new List<int>()); // T = List<int>
// 无法推断时需显式指定
// Print(null); // ❌ 编译错误:无法推断类型
Print<string>(null); // ✅ 显式指定三、泛型类
csharp
// 定义泛型类
public class Box<T>
{
private T content;
public void Put(T item)
{
content = item;
}
public T Get()
{
return content;
}
}
// 使用
Box<int> intBox = new Box<int>();
intBox.Put(42);
int value = intBox.Get(); // 类型安全,无需转换
Box<string> strBox = new Box<string>();
strBox.Put("Hello");
string text = strBox.Get();多个类型参数
csharp
public class Pair<TFirst, TSecond>
{
public TFirst First { get; set; }
public TSecond Second { get; set; }
public Pair(TFirst first, TSecond second)
{
First = first;
Second = second;
}
public void Display()
{
Console.WriteLine($"First: {First}, Second: {Second}");
}
}
var pair = new Pair<int, string>(1, "一");
pair.Display(); // 输出:First: 1, Second: 一
// 类型参数可以是任何类型
var pair2 = new Pair<string, DateTime>("现在", DateTime.Now);泛型类的静态成员
csharp
public class GenericCounter<T>
{
public static int Count = 0;
public GenericCounter()
{
Count++;
}
}
var intCounter1 = new GenericCounter<int>();
var intCounter2 = new GenericCounter<int>();
var strCounter1 = new GenericCounter<string>();
Console.WriteLine(GenericCounter<int>.Count); // 2(int 类型的独立计数)
Console.WriteLine(GenericCounter<string>.Count); // 1(string 类型的独立计数)
// ⚠️ 每个封闭类型(int、string)有各自的静态成员default 关键字
csharp
public class Factory<T>
{
private T[] items = new T[10];
public T GetDefault()
{
return default(T); // int→0, string→null, bool→false, 引用类型→null
}
public T CreateDefault()
{
return default; // C# 7.1+ 可省略类型参数
}
}
Console.WriteLine(default(int)); // 0
Console.WriteLine(default(string)); // null
Console.WriteLine(default(bool)); // False
Console.WriteLine(default(DateTime)); // 0001/1/1 0:00:00四、泛型约束
泛型约束限制了类型参数必须具备的条件。没有约束时,泛型参数只能使用 object 的成员。
约束类型对照表
| 约束 | 说明 | 示例用途 |
|---|---|---|
where T : struct | 值类型约束 | 数值计算、数学操作 |
where T : class | 引用类型约束 | 数据库实体、业务对象 |
where T : notnull | 不可为 null 类型 | 需要确保不为 null |
where T : new() | 无参构造函数 | 工厂模式、对象创建 |
where T : 基类名 | 指定基类或其派生类 | 动物继承体系 |
where T : 接口名 | 实现指定接口 | 比较、排序、释放 |
where T : U | 嵌套类型约束 | 类型参数之间的关系 |
csharp
// 值类型约束——适合数值处理
public class NumberProcessor<T> where T : struct
{
public T Value { get; set; }
// 可以安全使用值类型的特性
}
// 引用类型 + 无参构造函数——工厂模式
public class Factory<T> where T : class, new()
{
public T Create()
{
return new T(); // 可以安全创建实例
}
}
// 接口约束——保证类型具有特定方法
public class Sorter<T> where T : IComparable<T>
{
public T[] Sort(T[] array)
{
Array.Sort(array);
return array;
}
}
// 多约束组合
public class MultiConstraint<T> where T : class, IDisposable, new()
{
// T 必须是:引用类型 + 实现了 IDisposable + 有无参构造函数
}
// 枚举约束(C# 7.3+)
public class EnumParser<T> where T : struct, Enum
{
public T Parse(string value)
{
return (T)Enum.Parse(typeof(T), value);
}
}
// 委托约束(C# 7.3+)
public class EventWrapper<T> where T : Delegate
{
// T 必须是委托类型
}未约束泛型的限制
csharp
public class BadExample<T>
{
public void Process(T item)
{
// ❌ 不能使用 ==(除非约束为 class)
// if (item == null) ...
// ❌ 不能使用 +、-、*、/
// var result = item + item;
// ❌ 不能调用特定类型的方法
// item.ToString() 只能使用 object 的成员
}
}五、泛型接口
定义和实现
csharp
// 泛型接口
public interface IRepository<T>
{
T GetById(int id);
void Add(T item);
void Delete(int id);
IEnumerable<T> GetAll();
}
// 实现泛型接口(指定具体类型)
public class UserRepository : IRepository<User>
{
private List<User> users = new List<User>();
public User GetById(int id)
{
return users.Find(u => u.Id == id);
}
public void Add(User user)
{
users.Add(user);
}
public void Delete(int id)
{
users.RemoveAll(u => u.Id == id);
}
public IEnumerable<User> GetAll()
{
return users;
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}泛型接口与协变/逆变
csharp
// 协变接口(out):T 只能用于返回值
public interface IProducer<out T>
{
T Produce();
// void Consume(T item); // ❌ 编译错误:协变参数不能用于输入
}
// 逆变接口(in):T 只能用于参数
public interface IConsumer<in T>
{
void Consume(T item);
// T Produce(); // ❌ 编译错误:逆变参数不能用于输出
}
// 使用
IProducer<Dog> dogProducer = ...;
IProducer<Animal> animalProducer = dogProducer; // 协变:Dog → Animal
IConsumer<Animal> animalConsumer = ...;
IConsumer<Dog> dogConsumer = animalConsumer; // 逆变:Animal → Dog六、泛型委托
csharp
// 系统泛型委托
// Action:无返回值,0~16 个参数
Action greet = () => Console.WriteLine("Hello");
Action<string, int> showInfo = (name, age) =>
Console.WriteLine($"{name} 今年 {age} 岁");
// Func:有返回值,最后一个是返回值类型
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Func<string, int> getLength = s => s.Length;
// Predicate:返回 bool,用于条件判断
Predicate<int> isEven = n => n % 2 == 0;
Predicate<string> isNullOrEmpty = string.IsNullOrEmpty;
// Comparison:用于排序比较
Comparison<int> ascending = (a, b) => a.CompareTo(b);
Comparison<int> descending = (a, b) => b.CompareTo(a);七、协变和逆变
协变(Covariance,out 关键字)
允许将派生类型作为基类型返回。
csharp
// IEnumerable<T> 是协变的
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // ✅ 协变:string → object
// 数组也是协变的(但非类型安全)
string[] strArray = { "a", "b" };
object[] objArray = strArray; // ✅
objArray[0] = 123; // ❌ 运行时异常!ArrayTypeMismatchException逆变(Contravariance,in 关键字)
允许将基类型作为派生类型传入。
csharp
// Action<T> 是逆变的
Action<object> objAction = (obj) => Console.WriteLine(obj);
Action<string> strAction = objAction; // ✅ 逆变:object → string
strAction("Hello");
// IComparer<T> 也是逆变的
IComparer<object> objComparer = ...;
IComparer<string> strComparer = objComparer; // ✅协变/逆变对照表
| 特性 | 协变(out) | 逆变(in) |
|---|---|---|
| 关键字 | out | in |
| 方向 | T 只能输出(返回值) | T 只能输入(参数) |
| 类型转换 | 子类 → 基类 | 基类 → 子类 |
| 示例接口 | IEnumerable<out T> | IComparer<in T> |
| 示例委托 | Func<out T> | Action<in T> |
八、泛型集合性能对比
csharp
using System.Diagnostics;
// 性能对比:ArrayList vs List<T>
const int count = 10_000_000;
Stopwatch sw = Stopwatch.StartNew();
ArrayList arrayList = new ArrayList();
for (int i = 0; i < count; i++)
arrayList.Add(i); // 每次装箱
sw.Stop();
Console.WriteLine($"ArrayList 添加:{sw.ElapsedMilliseconds}ms");
sw.Restart();
List<int> list = new List<int>();
for (int i = 0; i < count; i++)
list.Add(i); // 无装箱
sw.Stop();
Console.WriteLine($"List<T> 添加:{sw.ElapsedMilliseconds}ms");
// 读取对比
sw.Restart();
for (int i = 0; i < count; i++)
{
int val = (int)arrayList[i]; // 每次拆箱
}
sw.Stop();
Console.WriteLine($"ArrayList 读取:{sw.ElapsedMilliseconds}ms");
sw.Restart();
for (int i = 0; i < count; i++)
{
int val = list[i]; // 无需拆箱
}
sw.Stop();
Console.WriteLine($"List<T> 读取:{sw.ElapsedMilliseconds}ms");
// 结果:List<T> 通常比 ArrayList 快 5-10 倍九、综合案例
案例一:泛型仓库模式
csharp
public interface IEntity
{
int Id { get; }
}
public class Product : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Customer : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// 泛型仓库
public class GenericRepository<T> where T : class, IEntity
{
private readonly Dictionary<int, T> _items = new();
public void Add(T item) => _items[item.Id] = item;
public T GetById(int id) => _items.GetValueOrDefault(id);
public IEnumerable<T> GetAll() => _items.Values;
public bool Delete(int id) => _items.Remove(id);
public int Count => _items.Count;
}
// 使用——不同类型复用同一套逻辑
var productRepo = new GenericRepository<Product>();
productRepo.Add(new Product { Id = 1, Name = "手机", Price = 2999 });
productRepo.Add(new Product { Id = 2, Name = "电脑", Price = 5999 });
var customerRepo = new GenericRepository<Customer>();
customerRepo.Add(new Customer { Id = 1, Name = "张三", Email = "zhang@test.com" });
customerRepo.Add(new Customer { Id = 2, Name = "李四", Email = "li@test.com" });
Console.WriteLine($"商品数量:{productRepo.Count}"); // 2
Console.WriteLine($"客户数量:{customerRepo.Count}"); // 2案例二:泛型缓存器
csharp
public class Cache<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _cache = new();
private readonly Dictionary<TKey, DateTime> _expiry = new();
private readonly TimeSpan _defaultTtl;
public Cache(TimeSpan defaultTtl)
{
_defaultTtl = defaultTtl;
}
public void Set(TKey key, TValue value, TimeSpan? ttl = null)
{
_cache[key] = value;
_expiry[key] = DateTime.Now.Add(ttl ?? _defaultTtl);
}
public bool TryGet(TKey key, out TValue value)
{
if (_cache.TryGetValue(key, out value) && _expiry[key] > DateTime.Now)
return true;
_cache.Remove(key);
value = default;
return false;
}
public void Clear() => _cache.Clear();
}
// 使用
var cache = new Cache<string, List<int>>(TimeSpan.FromMinutes(5));
cache.Set("scores", new List<int> { 85, 92, 78 });
if (cache.TryGet("scores", out var scores))
{
Console.WriteLine($"平均分:{scores.Average()}"); // 85
}案例三:泛型对象映射器
csharp
public static class Mapper
{
public static TTarget Map<TSource, TTarget>(TSource source)
where TTarget : new()
{
var target = new TTarget();
var sourceProps = typeof(TSource).GetProperties();
var targetProps = typeof(TTarget).GetProperties();
foreach (var sp in sourceProps)
{
var tp = Array.Find(targetProps, p => p.Name == sp.Name && p.PropertyType == sp.PropertyType);
if (tp != null && tp.CanWrite)
{
tp.SetValue(target, sp.GetValue(source));
}
}
return target;
}
}
// 使用
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class UserViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string ExtraInfo { get; set; }
}
var dto = new UserDto { Id = 1, Name = "张三", Email = "z@test.com" };
var vm = Mapper.Map<UserDto, UserViewModel>(dto);
Console.WriteLine($"{vm.Name} - {vm.Email}"); // 张三 - z@test.com十、泛型常见问题
1. 泛型不能做的事
csharp
public class GenericIssues<T>
{
// ❌ 不能直接实例化类型参数
// T item = new T(); // 除非有 new() 约束
// ❌ 不能使用运算符
// T result = a + b;
// ❌ 不能使用 typeof(T) 进行类型比较
// if (typeof(T) == typeof(int)) ...
// ❌ 不能将 T 用作特性参数类型
// [MyAttribute<T>] // 编译错误
// ✅ 可以使用 EqualityComparer<T>.Default
public bool AreEqual(T a, T b)
{
return EqualityComparer<T>.Default.Equals(a, b);
}
}2. 泛型与继承
csharp
// 开放类型和封闭类型
List<> // 开放类型(open type):未指定类型参数
List<int> // 封闭类型(closed type):已指定类型参数
// 开放类型不能实例化
// var list = new List<>(); // ❌ 编译错误
// 泛型类的继承
public class Base<T> { }
public class Derived : Base<int> { } // ✅ 指定类型参数
// public class Derived2 : Base<T> { } // ❌ 除非 Derived2 也是泛型
public class Derived3<T> : Base<T> { } // ✅ 传递类型参数核心知识点总结
泛型要素速查
| 要素 | 语法 | 示例 |
|---|---|---|
| 泛型方法 | Method<T>() | Swap<T>(ref T a, ref T b) |
| 泛型类 | class Class<T> | List<T>、Dictionary<TKey, TValue> |
| 泛型接口 | interface IInterface<T> | IEnumerable<T>、IComparable<T> |
| 泛型委托 | delegate T Del<T>() | Func<T>、Action<T> |
| 类型约束 | where T : 条件 | where T : class, new() |
| 协变 | out T | IEnumerable<out T> |
| 逆变 | in T | Action<in T> |
约束组合规则
| 组合 | 是否允许 | 示例 |
|---|---|---|
class + new() | ✅ | where T : class, new() |
struct + new() | ✅(struct 总是有默认构造) | where T : struct |
class + struct | ❌ 互斥 | — |
class + 基类 | ✅ | where T : class, Animal |
接口 + new() | ✅ | where T : IComparable, new() |
选择指南
| 场景 | 选择 |
|---|---|
| 需要处理多种类型的同一逻辑 | 泛型方法或泛型类 |
| 需要集合存储特定类型 | List<T>、Dictionary<TKey, TValue> |
| 需要数据访问的通用模式 | 泛型接口(如 IRepository<T>) |
| 需要类型安全的委托 | Action<T>、Func<T, R> |
| 需要为不同类型定义不同的行为 | 使用约束而非运行时类型检查 |
注意事项
- 泛型是编译时机制——编译器为每个值类型生成专用代码,为所有引用类型共享同一份代码
- 避免过度泛化——不是所有方法都需要泛型,必要时才使用
- 静态成员按封闭类型独立——
List<int>和List<string>的静态成员相互独立 - 性能优势在值类型最明显——避免装箱拆箱,引用类型差异较小
- 协变/逆变只适用于接口和委托——类不支持协变/逆变
- 尽量使用类型推断——编译器通常能推断泛型类型参数,使代码更简洁


