泛型进阶
大约 14 分钟约 4249 字
泛型进阶
简介
泛型是 C# 最强大的特性之一,允许编写类型无关的可复用代码。泛型进阶涵盖协变(Covariance)、逆变(Contravariance)、泛型约束、反射与泛型、默认值表达式等高级主题。掌握这些内容可以设计出更灵活、更安全的通用库和框架。
泛型的本质是"参数化类型"——将类型作为参数传递给类、接口、方法或委托。C# 的泛型是"具化泛型"(Reified Generics),运行时保留类型信息,这与 Java 的"类型擦除"(Type Erasure)截然不同。这意味着 C# 的泛型对值类型没有装箱开销,且可以在运行时通过反射获取泛型参数类型。
特点
泛型基础回顾
为什么需要泛型
// ==========================================
// 没有泛型的世界
// ==========================================
// 方案 1: 使用 object(不安全,有装箱)
public class ObjectStack
{
private readonly ArrayList _items = new();
public void Push(object item) => _items.Add(item);
public object Pop() => _items[_items.Count - 1];
}
// 问题: 值类型装箱/拆箱、类型不安全
var stack = new ObjectStack();
stack.Push(42); // 装箱 int → object
int value = (int)stack.Pop(); // 拆箱 object → int
stack.Push("hello");
string s = (string)stack.Pop(); // 如果类型不对,运行时才报错
// 方案 2: 为每种类型写一个类(代码重复)
public class IntStack { /* ... */ }
public class StringStack { /* ... */ }
// 维护噩梦!
// 方案 3: 泛型(类型安全 + 零装箱 + 代码复用)
public class Stack<T>
{
private readonly T[] _items = new T[100];
private int _index;
public void Push(T item) => _items[_index++] = item;
public T Pop() => _items[--_index];
}
// 完美!类型安全、无装箱、代码只写一次
// ==========================================
// 具化泛型 vs 类型擦除
// ==========================================
// C# (具化泛型):
// List<int> 和 List<string> 在运行时是不同的类型
// typeof(List<int>) != typeof(List<string>)
// Java (类型擦除):
// List<Integer> 和 List<String> 在运行时都是 List
// 运行时无法区分
// 具化泛型的优势:
// 1. 值类型无装箱
// 2. 运行时类型信息完整
// 3. 反射可以获取泛型参数协变与逆变
协变(out)
/// <summary>
/// 协变 — 泛型类型可以从派生类转换为基类
/// </summary>
// out 修饰符表示 T 只作为返回值
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
// 因为 out,可以做以下转换
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // 协变:string → object
// 自定义协变接口
public interface IResult<out T>
{
T Value { get; }
bool IsSuccess { get; }
}
public class SuccessResult<T> : IResult<T>
{
public T Value { get; }
public bool IsSuccess => true;
public SuccessResult(T value) => Value = value;
}
IResult<string> stringResult = new SuccessResult<string>("hello");
IResult<object> objectResult = stringResult; // 协变转换
// ==========================================
// 协变的原理和限制
// ==========================================
// 协变安全的前提:T 只出现在"输出"位置(返回值)
// 因为 string 可以安全地当作 object 使用
// 不安全的协变(编译器不允许)
// public interface IBad<out T>
// {
// void Set(T value); // T 在"输入"位置,不能用 out
// }
// 如果允许: IBad<string> → IBad<object>
// 然后 ibadObject.Set(42) 就能往 string 集合里放 int!类型不安全
// ==========================================
// 协变委托
// ==========================================
// Func<TResult> 的 TResult 是协变的
Func<string> getString = () => "hello";
Func<object> getObject = getString; // 协变
// 自定义协变委托
public delegate TResult MyFunc<out TResult>();逆变(in)
/// <summary>
/// 逆变 — 泛型类型可以从基类转换为派生类
/// </summary>
// in 修饰符表示 T 只作为参数
public interface IComparer<in T>
{
int Compare(T? x, T? y);
}
// 因为 in,可以做以下转换
IComparer<object> objectComparer = Comparer<object>.Default;
IComparer<string> stringComparer = objectComparer; // 逆变:object → string
// 自定义逆变接口
public interface IHandler<in TMessage>
{
void Handle(TMessage message);
}
public class ObjectHandler : IHandler<object>
{
public void Handle(object message) => Console.WriteLine(message);
}
IHandler<object> objHandler = new ObjectHandler();
IHandler<string> strHandler = objHandler; // 逆变:可以处理 string
// ==========================================
// 逆变的原理和限制
// ==========================================
// 逆变安全的前提:T 只出现在"输入"位置(参数)
// 因为处理 object 的处理器一定能处理 string
// 不安全的逆变(编译器不允许)
// public interface IBad<in T>
// {
// T Get(); // T 在"输出"位置,不能用 in
// }
// ==========================================
// 逆变的实际应用 — 事件处理器
// ==========================================
public class EventAggregator
{
// 能处理 EventArgs 的处理器也能处理 MouseEventArgs
private readonly List<Action<EventArgs>> _handlers = new();
public void Subscribe<THandler>(Action<THandler> handler)
where THandler : EventArgs
{
// 逆变: Action<MouseEventArgs> → Action<EventArgs>
_handlers.Add(handler!);
}
public void Publish(EventArgs args)
{
foreach (var handler in _handlers)
handler(args);
}
}
// ==========================================
// 逆变委托
// ==========================================
// Action<T> 的 T 是逆变的
Action<object> actObject = o => Console.WriteLine(o);
Action<string> actString = actObject; // 逆变
actString("hello"); // 调用 actObject("hello")协变和逆变速查
// ==========================================
// 常见接口的变体标注
// ==========================================
// 协变 (out):
// IEnumerable<out T>
// IEnumerator<out T>
// IReadOnlyCollection<out T>
// IReadOnlyList<out T>
// Func<out TResult>
// Task<out TResult>
// 逆变 (in):
// IComparer<in T>
// IEqualityComparer<in T>
// IComparable<in T>
// Action<in T>
// Predicate<in T>
// Comparison<in T>
// 不变 (无标注):
// IList<T> — 既有 Add(T) 又有 this[T]
// ICollection<T> — 同上
// Dictionary<K,V> — K 和 V 都不变
// List<T> — 同 IList<T>
// ==========================================
// 记忆口诀
// ==========================================
// 协变 (out): "输出"类型可以更具体 (子类 → 基类)
// IEnumerable<string> → IEnumerable<object>
// 因为 string 是 object,读出 string 当 object 用是安全的
// 逆变 (in): "输入"类型可以更通用 (基类 → 子类)
// IComparer<object> → IComparer<string>
// 因为比较 object 的方法一定能比较 string泛型约束
常用约束
/// <summary>
/// where 子句约束类型参数
/// </summary>
// class — 引用类型
public class Repository<T> where T : class
{
public T? Find(int id) => default;
}
// struct — 值类型
public class MathHelper<T> where T : struct, INumber<T>
{
public T Add(T a, T b) => a + b;
}
// new() — 有无参构造函数
public class Factory<T> where T : new()
{
public T Create() => new T();
}
// 特定基类
public class EntityService<T> where T : BaseEntity, new()
{
public int GetId(T entity) => entity.Id;
}
// 多约束
public class Validator<T> where T : class, IValidatable, new()
{
public bool Validate(T item) => item.Validate();
}
// 多类型参数约束
public class Converter<TSource, TTarget>
where TSource : class
where TTarget : class, new()
{
public TTarget Convert(TSource source)
{
var target = new TTarget();
// 映射逻辑
return target;
}
}
// 泛型之间的约束
public class Container<TItem, TCollection>
where TCollection : ICollection<TItem>, new()
{
public TCollection Items { get; } = new();
}
// notnull 约束 (.NET Core 3.0+)
public void Process<T>(T item) where T : notnull
{
// T 保证不为 null
}
// 类型参数约束 — 一个类型参数约束另一个
public class Comparer<T> where T : IComparable<T>
{
public T Max(T a, T b) => a.CompareTo(b) > 0 ? a : b;
}枚举约束(C# 7.3+)
/// <summary>
/// Enum / Delegate / unmanaged 约束
/// </summary>
// Enum 约束
public static string GetDisplayName<TEnum>(TEnum value) where TEnum : struct, Enum
{
return Enum.GetName(typeof(TEnum), value) ?? value.ToString();
}
// unmanaged 约束(非托管类型)
public static byte[] ToBytes<T>(T value) where T : unmanaged
{
int size = sizeof(T);
byte[] bytes = new byte[size];
MemoryMarshal.Write(bytes, ref value);
return bytes;
}
// Delegate 约束
public static MethodInfo GetMethod<TDelegate>(Expression<TDelegate> expression)
where TDelegate : Delegate
{
return ((MethodCallExpression)expression.Body).Method;
}C# 11 静态抽象成员
// ==========================================
// C# 11 静态抽象成员(Static Abstracts in Interfaces)
// ==========================================
// 允许接口包含静态抽象成员
// 实现泛型数学运算成为可能
// INumber<T> 接口定义了 +, -, *, / 等运算符
public static T Sum<T>(IEnumerable<T> values) where T : INumber<T>
{
T result = T.Zero; // 通过接口访问静态成员
foreach (var value in values)
result += value;
return result;
}
// 使用
int total = Sum(new[] { 1, 2, 3, 4, 5 }); // 15
double average = Sum(new[] { 1.5, 2.5, 3.5 }); // 7.5
// ==========================================
// 自定义泛型数学接口
// ==========================================
public interface IShape<T> where T : INumber<T>
{
T Area { get; }
T Perimeter { get; }
}
public class Circle<T> : IShape<T> where T : INumber<T>, ITrigonometricFunctions<T>
{
public T Radius { get; }
public Circle(T radius) => Radius = radius;
public T Area => T.Pi * Radius * Radius;
public T Perimeter => T.CreateChecked(2) * T.Pi * Radius;
}
// ==========================================
// IAdditionOperators 等运算符接口
// ==========================================
// .NET 7+ 提供了一系列运算符接口:
// IAdditionOperators<TSelf, TOther, TResult>
// ISubtractionOperators<TSelf, TOther, TResult>
// IMultiplyOperators<TSelf, TOther, TResult>
// IDivisionOperators<TSelf, TOther, TResult>
// IComparisonOperators<TSelf, TOther, TResult>
// IMinMaxValue<TSelf> — 提供 MinValue/MaxValue
public static T Clamp<T>(T value, T min, T max)
where T : IComparisonOperators<T, T, bool>
{
if (value < min) return min;
if (value > max) return max;
return value;
}
// 使用
int clamped = Clamp(150, 0, 100); // 100
double clampedDouble = Clamp(5.5, 0.0, 1.0); // 1.0泛型反射
运行时操作泛型
/// <summary>
/// 反射与泛型
/// </summary>
// 判断是否为泛型类型
Type type = typeof(List<string>);
bool isGeneric = type.IsGenericType; // true
bool isDef = type.IsGenericTypeDefinition; // false
Type def = type.GetGenericTypeDefinition(); // List<>
Type[] args = type.GetGenericArguments(); // [string]
// 从开放泛型创建封闭泛型
Type openGeneric = typeof(List<>);
Type closedGeneric = openGeneric.MakeGenericType(typeof(int)); // List<int>
// 通过反射调用泛型方法
var method = typeof(Enumerable).GetMethod("Where")!
.MakeGenericMethod(typeof(int));
// 动态创建泛型实例
public object CreateRepository(Type entityType)
{
var repoType = typeof(Repository<>).MakeGenericType(entityType);
return Activator.CreateInstance(repoType)!;
}
// ==========================================
// 泛型方法的反射调用
// ==========================================
public static class GenericInvoker
{
// 通过反射调用泛型方法
public static object CallGenericMethod(
Type targetType,
string methodName,
Type genericArg,
object? instance = null,
params object[] parameters)
{
var method = targetType.GetMethod(methodName)
?? throw new MissingMethodException(methodName);
var closedMethod = method.MakeGenericMethod(genericArg);
return closedMethod.Invoke(instance, parameters)!;
}
}
// 使用
var result = GenericInvoker.CallGenericMethod(
typeof(MyService), "GetById", typeof(User), myServiceInstance, 42);泛型缓存模式
每类型静态缓存
/// <summary>
/// 泛型类的静态字段每个类型参数独立
/// </summary>
public class TypeCache<T>
{
// 每个 T 有独立的 _instance
public static readonly T Instance;
static TypeCache()
{
// 每个 T 只执行一次
Instance = (T)Activator.CreateInstance(typeof(T))!;
}
}
// 泛型缓存实现快速类型映射
public static class FastEnum<T> where T : struct, Enum
{
public static readonly T[] Values = Enum.GetValues<T>();
public static readonly string[] Names = Enum.GetNames<T>();
public static readonly Dictionary<T, string> NameMap =
Values.Zip(Names).ToDictionary(x => x.First, x => x.Second);
}
// 使用
var names = FastEnum<DayOfWeek>.NameMap; // 首次调用时初始化,之后直接读缓存
// ==========================================
// 泛型缓存的更多应用
// ==========================================
// 泛型描述符缓存 — 避免反射开销
public static class TypeDescriptor<T>
{
public static readonly PropertyInfo[] Properties =
typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
public static readonly Dictionary<string, PropertyInfo> PropertyMap =
Properties.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
public static readonly ConstructorInfo? EmptyConstructor =
typeof(T).GetConstructor(Type.EmptyTypes);
public static T CreateInstance()
{
if (EmptyConstructor == null)
throw new InvalidOperationException($"类型 {typeof(T)} 没有无参构造函数");
return (T)EmptyConstructor.Invoke(null)!;
}
}
// 泛型比较器缓存
public static class ComparerCache<T> where T : class
{
public static readonly Func<T, T, bool> EqualsFunc;
public static readonly Func<T, int> GetHashCodeFunc;
static ComparerCache()
{
// 根据类型选择最快的比较方式
if (typeof(IEquatable<T>).IsAssignableFrom(typeof(T)))
{
EqualsFunc = (a, b) => ((IEquatable<T>)a).Equals(b);
}
else
{
EqualsFunc = (a, b) => EqualityComparer<T>.Default.Equals(a, b);
}
GetHashCodeFunc = obj => obj?.GetHashCode() ?? 0;
}
}默认值表达式
default 与 new()
// ==========================================
// 泛型中的默认值
// ==========================================
// default(T) — T 的默认值
// 引用类型: null
// 值类型: 全零(int→0, bool→false, DateTime→0001-01-01)
public static T GetDefault<T>()
{
return default(T); // C# 7.1+ 可以简写为 default
}
// default 表达式
public static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
// 如果 a 或 b 是 default(T)(如 null),会抛 NullReferenceException
}
// ==========================================
// 泛型中的 new() 约束
// ==========================================
public class Factory<T> where T : new()
{
public T Create() => new T(); // 编译器知道 T 有无参构造函数
}
// 不使用 new() 约束,使用 Activator
public class LazyFactory<T>
{
public T Create()
{
return (T)Activator.CreateInstance(typeof(T))!;
// 运行时检查,如果 T 没有无参构造函数会抛异常
}
}
// ==========================================
// C# 10 default(T) 可以省略类型参数
// ==========================================
T value = default; // 等价于 default(T)泛型方法的类型推断
类型推断规则
// ==========================================
// 编译器如何推断泛型类型参数
// ==========================================
// 简单推断
var list = new List<int>(); // 推断 List<int>
var dict = new Dictionary<string, int>(); // 推断 Dictionary<string, int>
// 方法类型推断
public static T Max<T>(T a, T b) where T : IComparable<T> { /* ... */ }
var result = Max(3, 5); // 推断 T = int
// 多参数推断
public static TResult Map<TSource, TResult>(TSource source, Func<TSource, TResult> mapper)
=> mapper(source);
var mapped = Map(42, x => x.ToString()); // 推断 TSource=int, TResult=string
// 推断失败的情况 — 需要显式指定
public static T Create<T>() => Activator.CreateInstance<T>()!;
var obj = Create<List<int>>(); // 必须显式指定,无法推断
// 匿名方法的类型推断
public static void Apply<T>(T[] array, Action<T> action)
{
foreach (var item in array) action(item);
}
Apply(new[] { 1, 2, 3 }, x => Console.WriteLine(x * 2)); // 推断 T = int
// ==========================================
// 返回类型推断(C# 10+)
// ==========================================
// 从返回值推断
// C# 10+ 可以在 return 语句中推断
// 但构造函数的类型参数仍然需要显式指定优点
缺点
总结
泛型进阶核心:协变(out)用于返回类型、逆变(in)用于参数类型、where 约束限制类型参数。泛型缓存模式利用静态字段按类型隔离的特性实现高性能缓存。C# 11 的静态抽象成员和 INumber 接口让泛型数学运算成为可能。
核心原则:
- 优先使用泛型 — 避免装箱和类型不安全
- 正确使用协变逆变 — out 用于只读位置,in 用于只写位置
- 合理约束 — 足够的约束提高 API 可用性,但不要过度约束
- 利用泛型缓存 — 避免反射的重复开销
- C# 11+ 用静态抽象 — 替代旧式的接口约束模式
关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道"为什么这样写"和"在什么边界下不能这样写"。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
- 试图对
List<T>使用协变(List<T>是不变的,只有IEnumerable<T>是协变的)。 - 泛型约束过于严格导致 API 不灵活。
- 在泛型方法中 new T() 但忘记加 new() 约束。
- 滥用协变逆变导致运行时类型异常。
- 忽略泛型缓存模式,在循环中重复反射。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
- 学习 .NET 运行时如何为不同值类型生成特化代码。
- 研究 Source Generator 与泛型的结合使用。
适用场景
- 当你准备把《泛型进阶》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了"高级"而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
- 确认泛型约束是否满足(特别是 new() 和 struct 约束)。
复盘问题
- 如果把《泛型进阶》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《泛型进阶》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《泛型进阶》最大的收益和代价分别是什么?
