C# 泛型 (Generics)
1. 概念
泛型主要用于类与方法上,使用占位符(通常是 T)来代表某种类型,在编译期间决定其具体类型。
- 类名称后面加
<T>代表该类就是泛型类。 - 占位符不一定是
T,也可以是<M>,<Myt>等,但习惯上使用T(Type)。 - 泛型在实例化时(或使用时),占位符
T会被特化成指定的类型。
为什么要使用泛型?
<T> 是类型占位符,表示还没决定具体是什么类型,先把这个位置占了。
场景举例:
当不确定类里面是 int[] 类型数组,还是 string[] 类型数组,或者是其他类型的数组时,使用泛型可以避免重复写多个逻辑相同的类。
2. 泛型类
这是一个模拟栈(Stack)内存空间的泛型类示例。
class zhan<T> { // 看做栈内存空间 private T[] zhan_Cpu = new T[300]; // 将数据存入栈的方法 public void set(T valu, int index) { zhan_Cpu[index] = valu; // 数据入栈 } // 将数据取出栈的方法 public T get(int index) { return zhan_Cpu[index]; // 数据取出 } } // 使用示例: class Program { static void Main(string[] args) { // 1. 对泛型类 zhan 的特化、特化为 int 类型的类 zhan<int> int_data = new zhan<int>(); // 此时 int_data 对象内的数组 zhan_Cpu 读写都是 int 类型 // 2. 特化为 string 类型 zhan<string> str_data = new zhan<string>(); // 此时 str_data 对象内的数组 zhan_Cpu 读写都是 string 类型 // 3. 特化为 double 类型 zhan<double> doub_data = new zhan<double>(); // 此时 doub_data 对象内的数组 zhan_Cpu 读写都是 double 类型 } }
3. 泛型方法与 dynamic
格式:无返回值
void fangfa<T>(T a, T b) { // 方法体 }
格式:有返回值与 dynamic
在泛型中直接进行算术运算(如 a + b)通常会报错,因为编译器不知道 T 是否支持相加。可以使用 dynamic 关键字解决。
public T fangfa1<T>(T a, T b) { // return a + b; // 报错:运算符不能用于两个 T 类型的操作数 // 原因:编译器会在编译时对参数 a, b 校验,无法确定 T 是否支持 + 运算 dynamic da = a; // dynamic 将类型校验推延到运行时 dynamic db = b; // 当运行时,已经为 T 类型赋予了 int 或者 double 类型,此时可以相加 return da + db; }
dynamic (动态)
- 发音:/daɪˈnæ mɪk/
- 作用:当运行时根据传过来的值确定其类型,跳过编译器的静态检查。
4. 多样化泛型
泛型可以定义多个占位符,用于处理多种不同类型的数据。
class Program { static void Main(string[] args) { // 实例化时,分别指定 T=int, U=string, F=double reyy<int, string, double> Fanxing = new reyy<int, string, double>(); Fanxing.LeiXing_1[1] = 500; // T is int Fanxing.LeiXing_2[1] = "ABSD"; // U is string Fanxing.LeiXing_3[1] = 63.588; // F is double } } // 多样化泛型类 class reyy<T, U, F> { public T[] LeiXing_1 = new T[300]; public U[] LeiXing_2 = new U[300]; public F[] LeiXing_3 = new F[300]; }
5. 泛型的继承
泛型类也可以被继承,主要分为两种情况。
情况 1:普通类继承泛型
在继承时,直接特化泛型类型(确定父类的类型)。
class Program { static void Main(string[] args) { teacher Teacher = new teacher(); // 实例化老师对象 Teacher.id = 550346; // 老师作为人类的 id (已经是 int 类型) } } class people<T> { // 编号属性 public T id { get; set; } } // 继承一个泛型父类、继承的同时特化泛型为 int class teacher : people<int> { }
情况 2:泛型继承泛型
子类也是泛型,当子类被实例化时,特化的类型会传导给父类。
class Program { static void Main(string[] args) { // 实例化老师对象,特化为 int // 把子类特化 int 类型传导给父类 T teacher<int> Teacher = new teacher<int>(); Teacher.id = 550346; // 老师作为人类的 id } } class people<T> { // 编号属性 public T id { get; set; } } // 当无法确定子类的类型时、可以把子类看做泛型类 // 当子类实例化时、特化的类型 B 也会传导给父类 T class teacher<B> : people<B> { }
6. 泛型约束 (Constraints)
默认情况下,泛型 T 可以是任何类型。但在某些场景下,我们需要限制 T 必须具备某些特征(例如:必须是引用类型、必须实现了某个接口、或者必须有一个无参构造函数)。
这时就需要使用 where 关键字。
常用约束列表
| 约束语法 | 说明 |
|---|---|
where T : struct | 类型参数必须是值类型 (如 int, float, struct)。 |
where T : class | 类型参数必须是引用类型 (如 string, class, interface)。 |
where T : new() | 类型参数必须有一个无参数的公共构造函数。此约束允许在泛型类中创建 T 的实例 (new T())。 |
where T : <基类名> | 类型参数必须是指定的基类,或者是派生自该基类的子类。 |
where T : <接口名> | 类型参数必须实现指定的接口。 |
示例代码
// 1. 接口约束示例 // 只有实现了 IDBItem 接口的类,才能作为 T 传入 public bool IsExist<T>(string TableName = null) where T : IDBItem { // 因为加了约束,编译器知道 T 一定有 IDBItem 的特性 return true; } // 2. 构造函数约束示例 // 只有包含无参构造函数的类,才能作为 T 传入 class Factory<T> where T : new() { public T CreateInstance() { // 如果没有 new() 约束,这里写 new T() 会报错 return new T(); } } // 3. 组合约束 // T 必须同时满足:是引用类型、实现了 IDisposable 接口、且有无参构造函数 class MyManager<T> where T : class, IDisposable, new() { }

评论