C# 泛型完整博文
标题:【C#零基础】泛型到底是什么?通俗易懂详解泛型(泛型方法/泛型类/泛型约束)
前言
初学C#时,我们经常使用List<T>,很多人只会调用,却不理解泛型设计的意义。
本文从痛点入手,一步步讲解泛型概念、使用场景、优势以及泛型约束,适合零基础初学者阅读。
一、没有泛型之前,我们面临什么问题?
假设现在需要封装一个“容器”,用来存放数据。在泛型诞生之前,主流两种写法,都有明显缺陷。
方式1:写大量重载方法/专属类
想要支持int、string、自定义Book类,就要不断复制代码,创建Int容器、String容器。
publicvoidShow(intdata){Console.WriteLine(data);}publicvoidShow(stringdata){Console.WriteLine(data);}缺点:逻辑完全一致,大量重复代码,后期维护成本极高。
方式2:使用object接收所有类型
所有类型都隐式向上转为object,一套方法兼容全部类型。
publicvoidShow(objectdata){Console.WriteLine(data);}但是隐患非常大:
- 装箱拆箱:值类型(int、double)存入object会产生装箱,取出时拆箱,存在性能损耗;
- 类型不安全:编译时不会校验类型,运行时才可能爆出类型转换异常;
- 可读性差:无法直观看出容器里面存放什么类型。
泛型诞生的目标:一套逻辑复用、编译期类型安全、避免装箱拆箱性能损耗。
二、什么是泛型?
泛型 = 类型参数化
定义类/方法时,先用占位符(约定名称T,代表Type)代替真实数据类型;等到调用代码的时候,再指定具体类型。
通俗理解:先预留“类型空位”,使用的时候再把真实类型填进去。
三、泛型方法(入门首选)
语法:方法名称后面添加<T>声明类型占位符
/// <summary>/// 泛型方法/// </summary>/// <typeparam name="T">类型占位符</typeparam>/// <param name="data">任意类型数据</param>publicstaticvoidShow<T>(Tdata){Console.WriteLine(data);}调用方式:
Show<int>(100);Show<string>("Hello C#");Show<Book>(newBook());编译器会根据传入的类型,自动生成对应专属版本代码,编译阶段就能校验类型是否合法。
四、泛型类(开发最常用,List就是泛型类)
我们自己实现一个简易通用容器:
// 泛型类,T代表存放数据的类型publicclassBox<T>{publicTValue{get;set;}}使用示例:
// 存放intBox<int>intBox=newBox<int>();intBox.Value=99;// 存放字符串Box<string>strBox=newBox<string>();strBox.Value="泛型测试";我们天天使用的集合List<T>就是.NET内置泛型类:
List<int>numList=newList<int>();List<string>strList=newList<string>();对比老式ArrayList(基于object):
// 不推荐!可以随意混放数据ArrayListarr=newArrayList();arr.Add(1);arr.Add("测试");// 推荐泛型集合,编译限制只能存放intList<int>list=newList<int>();list.Add(1);// list.Add("测试"); // 直接编译报错,提前规避bug五、泛型约束 where T:xxx(重点考点)
默认情况下T可以是任意类型。
泛型约束用来限制占位符T的范围,告诉编译器这个类型具备哪些能力,否则编译器无法直接操作T。
常用约束清单
where T : class:T必须是引用类型(类)where T : struct:T必须是值类型(结构体)where T : new():T必须拥有无参公共构造函数where T : 父类名称:T必须继承该父类where T : 接口名称:T必须实现指定接口
示例:无参构造约束
// 约束:T必须有无参构造函数,才能new T()publicstaticTCreateInstance<T>()whereT:new(){returnnewT();}六、泛型三大核心优势
- 类型安全:编译阶段校验类型,提前拦截类型错误,避免运行时异常;
- 代码复用:同一套业务逻辑,支持多种数据类型,消除大量重复代码;
- 高性能:值类型不会发生装箱、拆箱操作,相比object方案效率更高。
七、初学者高频误区澄清
- 误区:泛型是动态类型
纠正:泛型类型在编译阶段就确定,不是运行时动态切换。 - 误区:T是一个真实数据类型
纠正:T只是占位符;Box<int>、Box<string>是两个完全不同的封闭类型。 - 误区:可以直接写
T num = 0;
纠正:编译器不知道T是什么类型,无法直接赋值,需要添加约束后才能操作。 - 误区:泛型等价于object
纠正:object是统一向上转型,运行时检查;泛型编译生成专属代码,编译期检查。
八、总结
泛型本质就是把数据类型当作参数,实现类型参数化。
解决了传统object方案类型不安全、性能差,以及大量重载重复代码的痛点。
日常开发使用优先级:泛型方法>泛型类>泛型约束
最常见落地场景:List<T>集合、通用工具类、仓储泛型封装。