csharp:类:泛型

C# 泛型 (Generics)

泛型主要用于类与方法上,使用占位符(通常是 T)来代表某种类型,在编译期间决定其具体类型

  • 类名称后面加 <T> 代表该类就是泛型类。
  • 占位符不一定是 T,也可以是 <M>, <Myt> 等,但习惯上使用 T (Type)。
  • 泛型在实例化时(或使用时),占位符 T 会被特化成指定的类型。

<T> 是类型占位符,表示还没决定具体是什么类型,先把这个位置占了。

场景举例: 当不确定类里面是 int[] 类型数组,还是 string[] 类型数组,或者是其他类型的数组时,使用泛型可以避免重复写多个逻辑相同的类。

这是一个模拟栈(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 类型
   }
}
void fangfa<T>(T a, T b)
{
    // 方法体
}

在泛型中直接进行算术运算(如 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/
  • 作用:当运行时根据传过来的值确定其类型,跳过编译器的静态检查。

泛型可以定义多个占位符,用于处理多种不同类型的数据。

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];
}

泛型类也可以被继承,主要分为两种情况。

在继承时,直接特化泛型类型(确定父类的类型)。

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> 
{
}

子类也是泛型,当子类被实例化时,特化的类型会传导给父类。

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>
{
}

默认情况下,泛型 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()
{
}

泛型总结示意图

请输入您的评论. 可以使用维基语法:
 

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/类/泛型.txt
  • 最后更改: 2025/11/27 17:25
  • 张叶安