一、泛型核心概念(原文背诵)
泛型:广泛的不确定类型
定义方法时,不想固定参数类型、返回值类型,可以定义为泛型类型 T。
核心思想:延迟指定类型
定义方法时不锁定类型,调用方法时传入具体类型,一套代码适配所有数据类型。
二、普通方法重载(泛型优化前)
代码
//int专用方法
static int Test(int a)
{
return 10;
}
//string专用方法
static string Test(string a)
{
return "ss";
}
问题痛点
如果需要支持 int、string、bool、double 等多种类型,需要写无数个重载方法,代码冗余、重复度极高。
泛型就是为了解决:不同类型、相同逻辑,不用重复写代码
三、单泛型方法(核心基础)
泛型方法定义
//<T> 声明泛型占位符
static T Test<T>(T a)
{
return a;
}
语法规则(必背)
-
<T>:声明泛型,T 是类型占位符,可以换任意字母(A、B、T1、T2) -
方法中所有T 代表同一个类型
-
参数是 T、返回值也是 T,做到:传入什么类型,返回什么类型
调用方式
// 1.显式指定类型
Console.WriteLine(Test<int>(10));
Console.WriteLine(Test<string>("aa"));
Console.WriteLine(Test<bool>(true));
// 2.泛型类型推断(简化写法)
Console.WriteLine(Test(new string[] { "aa" }));
两种调用方式解析
显式调用 :手动写 <类型>,清晰直观
隐式推断 :编译器根据实参自动识别类型,可以省略 <>
上面代码推断结果:string[] 类型
四、同类型双参数泛型方法 Test1<T>
代码定义
//两个参数必须是相同类型
static T Test1<T>(T a, T b)
{
return b;
}
规则
只有一个泛型占位符 T,所以 a、b 必须是一模一样的类型
调用代码
Test1<int>(10, 20); //显式指定int
Test1(10, 20); //编译器自动推断int
五、双泛型参数方法 Test2<T1,T2>
代码定义
//两个不同泛型,无返回值
static void Test2<T1,T2>(T1 a,T2 b)
{
}
核心规则
T1、T2 是两个独立类型,可以相同、可以不同
调用解析
Test2<int, string>(10, "aa"); //T1=int T2=string
Test2(10, new string[] { "aa" });//T1=int T2=string[]
Test2(10,10);//T1=int T2=int(两个泛型可以相同)
六、返回值+数组泛型方法 Test3<T1,T2>
代码定义
//T1为返回值类型、第一个参数类型
//T2[] 代表泛型数组
static T1 Test3<T1,T2>(T1 a, T2[] b)
{
return a;
}
参数解析
-
第一个参数:普通泛型 T1
-
第二个参数:泛型数组 T2\[\]
-
返回值固定为 T1 类型
调用解析
Test3<int, int>(10, new int[] { 1, 2, 3 });
//T1=int T2=int int数组
Test3(10, new string[] { "aa" });
//编译器自动推断 T1=int T2=string string数组
七、泛型两大调用方式(必考)
1. 显式调用
手动指定类型:方法名<类型>(参数)
适合:类型模糊、编译器无法推断的场景
2. 隐式类型推断(简化)
直接写参数,省略 <>
编译器根据实参自动匹配泛型类型
开发中最常用
八、单泛型 VS 多泛型 核心区别
1. 单个泛型 <T>
所有 T 类型必须统一
适合:参数、返回值类型一致的场景
2. 多个泛型 <T1,T2>
T1、T2 相互独立
可以一同一不同,适配多个不同类型参数场景
九、泛型满分简答题(直接背)
1. 什么是泛型?
泛型是一种延迟确定数据类型的技术,定义方法时不固定参数和返回值类型,通过 <T> 声明占位类型,在调用方法时根据传入参数确定具体类型,实现一套代码适配多种数据类型,解决代码冗余问题。
2. 泛型的优点?
避免方法重载冗余、代码复用性高、保证类型安全、无需装箱拆箱、程序效率更高。
3. 多个泛型参数的特点?
多个泛型占位符相互独立,类型可以相同也可以不同,灵活适配多类型参数场景。
十、终极背诵口诀
泛型T是占位符,定义不把类型束
调用传参定类型,延迟绑定真舒服
单T类型必须同,多T独立互不冲
可推断可显式,一套代码通万物
---------泛型效率(普通类型 VS Object装箱 VS 泛型)------------
一、代码功能概述
本案例通过 Stopwatch 计时器,循环执行海量次数,对比三种方法的执行效率:
-
Test1:固定值类型参数
int -
Test2:父类对象参数
object(存在装箱拆箱) -
Test3:泛型参数
T
通过实际运行耗时,验证泛型无装箱拆箱、效率接近原生值类型的核心特性。
二、完整运行结果(实测标准数据)
循环次数:1亿次
-
普通值类型方法 Test1:3ms
-
泛型方法 Test3:4ms
-
object装箱方法 Test2:10ms(最慢)
效率排行:固定值类型 > 泛型 > object装箱
三、三种方法逐一带底层原理解析
1. 普通值类型方法 Test1(int a)
static void Test1(int a)
{
}
底层原理
参数直接接收 栈内存值类型,无需类型转换、无需装箱、无需拆箱。
编译时类型已经确定,运行时直接执行,效率最高。
优缺点
✅ 效率极高,无性能损耗
❌ 只能适配单一类型,复用性极差,每种类型都要重载方法
2. Object 参数方法 Test2(object a)
static void Test2(object a)
{
}
底层原理(重难点)
C# 中 object 是所有类型的父类。
当 int 10 传给 object 参数时,发生:装箱(装箱转换)
装箱过程:
-
在堆中开辟新内存
-
将栈中 int 值拷贝到堆内存
-
栈上存储堆地址引用
海量循环下,频繁装箱会产生大量 GC垃圾内存,极大拖慢效率。
优缺点
✅ 可以接收任意类型,复用性高
❌存在装箱拆箱,性能极差
3. 泛型方法 Test3<T>(T a)
static void Test3<T>(T a)
{
}
底层原理(核心考点)
泛型的本质:编译时预留类型占位,运行时根据传入类型生成专属方法。
当传入 int 类型时,CLR 会自动生成一个 Test3(int a) 专属方法。
全程无装箱、无拆箱,直接操作值类型。
优缺点
✅ 完全没有装箱拆箱损耗,效率无限接近原生值类型
✅ 一套代码适配所有类型,复用性极强
❌ 相比原生固定类型,有极其微弱的初始化损耗(几乎可忽略)
四、核心结论(必背简答题)
1. 为什么泛型比 object 效率高?
object 参数接收值类型会发生装箱拆箱 ,产生堆内存开销和 GC 压力;泛型在运行时会根据具体类型生成专属方法,全程无装箱拆箱,性能大幅提升。
2. 泛型和普通固定类型对比?
普通固定类型效率最高,但无法复用;泛型效率几乎持平原生类型,同时解决了代码复用问题,兼顾性能与复用性。
3. 泛型最大优势总结
1. 类型安全(编译时报错,不会类型转换异常)
2. 避免装箱拆箱,性能极高
3. 一套代码适配多类型,彻底解决重载冗余
五、三者终极对比表(秒杀考点)
| 方法类型 | 是否装箱拆箱 | 执行效率 | 代码复用性 | 适用场景 |
|---|---|---|---|---|
| 固定值类型 | 无 | 最高 | 差 | 单一固定类型逻辑 |
| Object 参数 | 有(严重损耗) | 最低 | 高 | 老旧框架、弱类型场景(不推荐) |
| 泛型方法 | 无 | 极高 | 最高 | 通用工具方法、集合类、公共逻辑 |
六、终极背诵口诀
固定类型最快行,不能复用代码长
Object装箱速度慢,频繁GC性能伤
泛型无箱性能强,复用安全两头香