====== C# 泛型 (Generics) ====== ===== 1. 概念 ===== 泛型主要用于类与方法上,使用占位符(通常是 ''T'')来代表某种类型,**在编译期间决定其具体类型**。 * 类名称后面加 '''' 代表该类就是泛型类。 * 占位符不一定是 ''T'',也可以是 '''', '''' 等,但习惯上使用 ''T'' (Type)。 * 泛型在实例化时(或使用时),占位符 ''T'' 会被**特化**成指定的类型。 ==== 为什么要使用泛型? ==== '''' 是类型占位符,表示还没决定具体是什么类型,先把这个位置占了。 **场景举例**: 当不确定类里面是 ''int[]'' 类型数组,还是 ''string[]'' 类型数组,或者是其他类型的数组时,使用泛型可以避免重复写多个逻辑相同的类。 ===== 2. 泛型类 ===== 这是一个模拟栈(Stack)内存空间的泛型类示例。 class zhan { // 看做栈内存空间 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_data = new zhan(); // 此时 int_data 对象内的数组 zhan_Cpu 读写都是 int 类型 // 2. 特化为 string 类型 zhan str_data = new zhan(); // 此时 str_data 对象内的数组 zhan_Cpu 读写都是 string 类型 // 3. 特化为 double 类型 zhan doub_data = new zhan(); // 此时 doub_data 对象内的数组 zhan_Cpu 读写都是 double 类型 } } ===== 3. 泛型方法与 dynamic ===== ==== 格式:无返回值 ==== void fangfa(T a, T b) { // 方法体 } ==== 格式:有返回值与 dynamic ==== 在泛型中直接进行算术运算(如 ''a + b'')通常会报错,因为编译器不知道 ''T'' 是否支持相加。可以使用 ''dynamic'' 关键字解决。 public T fangfa1(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 Fanxing = new reyy(); 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 { 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 { // 编号属性 public T id { get; set; } } // 继承一个泛型父类、继承的同时特化泛型为 int class teacher : people { } ==== 情况 2:泛型继承泛型 ==== 子类也是泛型,当子类被实例化时,特化的类型会**传导**给父类。 class Program { static void Main(string[] args) { // 实例化老师对象,特化为 int // 把子类特化 int 类型传导给父类 T teacher Teacher = new teacher(); Teacher.id = 550346; // 老师作为人类的 id } } class people { // 编号属性 public T id { get; set; } } // 当无法确定子类的类型时、可以把子类看做泛型类 // 当子类实例化时、特化的类型 B 也会传导给父类 T class teacher : people { } ===== 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(string TableName = null) where T : IDBItem { // 因为加了约束,编译器知道 T 一定有 IDBItem 的特性 return true; } // 2. 构造函数约束示例 // 只有包含无参构造函数的类,才能作为 T 传入 class Factory where T : new() { public T CreateInstance() { // 如果没有 new() 约束,这里写 new T() 会报错 return new T(); } } // 3. 组合约束 // T 必须同时满足:是引用类型、实现了 IDisposable 接口、且有无参构造函数 class MyManager where T : class, IDisposable, new() { } ===== 7. 总结 ===== {{.:pasted:20250928-163412.png?500|泛型总结示意图}}