泛型是编译期与运行时协同的类型机制,非语法糖;错误使用会导致invalidcastexception等真实错误;类型参数名应遵循t、tkey等约定;约束是类型安全必需;泛型方法推断失败需显式指定;list与arraylist存在装箱/类型安全等本质差异;相同实参的泛型实例互不兼容,协变逆变仅限接口委托。

泛型不是语法糖,是编译期+运行时共同参与的类型系统机制。用错地方会引发 InvalidCastException、约束不匹配、或泛型实例间无法隐式转换等真实错误,而不是“写起来方便”那么简单。
泛型类定义时怎么选类型参数名和约束
类型参数名用单个大写字母(T、TKey、TValue)是 C# 约定,不是强制,但别写成 MyType 或 DataType —— 这会让调用方困惑,也违反 IDE 的智能提示逻辑。
约束不是可选项,是类型安全的护栏:
-
where T : class:当你内部要对T做as转换或判空(if (t == null))时必须加,否则值类型传入会编译失败 -
where T : struct:仅用于需要值语义且禁止 null 的场景(如自定义数值包装器),但注意Nullable<t></t>不满足该约束 -
where T : new():只在你要用new T()实例化时才加,且必须配合其他约束(如class或struct)才能保证构造函数存在 - 多个约束用逗号分隔,顺序无关,但
new()必须放最后
泛型方法调用时为什么有时要显式写 <t></t>
编译器能自动推断类型,前提是所有实参都携带明确类型信息。一旦出现以下情况,GetFirst<string>(list)</string> 这种显式写法就绕不开:
- 方法参数是
object或基类(如IEnumerable而非IEnumerable<string></string>) - 返回值参与了类型敏感的上下文(比如赋值给
var但左侧已有类型推导歧义) - 重载方法中多个泛型版本共存,编译器无法唯一确定该选哪个
T - 调用的是静态泛型方法且无参数(
typeof(T)类型查询场景),此时根本没法推断
典型反例:Console.WriteLine(GetFirst(new object[] { "a", "b" })) —— 数组类型是 object[],GetFirst 推不出 T 是 string,编译报错。
List<t></t> 和 ArrayList 的区别不只是“有没有尖括号”
这不是风格差异,是底层行为断层:
-
List<int></int>在 IL 中生成的是专用于int的代码,元素直接存栈上,无装箱;ArrayList存的是object引用,每个int都要装箱,取出来还要拆箱 -
List<t></t>的foreach使用IEnumerator<t></t>,迭代器返回T本身;ArrayList的foreach返回object,强制转型开销藏在循环里 -
ArrayList允许混存int、string、null,但你在for循环里取第 5 个元素时,永远不知道它到底是什么类型 —— 编译器不拦,运行时崩 - .NET 6+ 中
ArrayList已被标记为“遗留”,官方文档明确建议禁用
泛型类型实参相同时,不同泛型类实例之间不兼容
这是最容易被忽略的运行时事实:Stack<int></int> 和 Queue<int></int> 是两个完全无关的类型,哪怕都用 int;Repository<user></user> 和 Repository<admin></admin> 也不能互相赋值,哪怕 Admin 继承自 User。
协变(out T)和逆变(in T)只适用于接口和委托,且有严格限制:
-
IEnumerable<string></string>可隐式转为IEnumerable<object></object>(因为IEnumerable<out t></out>) -
Action<object></object>可隐式转为Action<string></string>(因为Action<in t></in>) - 但
List<string></string>不能转List<object></object>—— 它既不是协变也不是逆变,它是不变的(invariant) - 自己写的泛型类默认都是 invariant,加
out/in需要手动声明接口并实现
所以别试图把 Dictionary<string product></string> 当作 Dictionary<string object></string> 传参,会编译失败 —— 这不是 bug,是设计如此。











