当前位置:首页 > 技术 > 正文内容

C# 泛型详解:概念、泛型类、类型参数与约束

访客 技术 2026年10月24日 1

泛型编程允许开发者编写能够处理多种数据类型的通用代码,而无需为每种特定类型重复编写相同的逻辑。通过引入抽象层,泛型大大提高了代码的可重用性和可维护性。

C# 中的泛型

C# 语言通过泛型(Generics)提供了类型参数化的能力。这意味着可以编写代码模板,然后在创建具体类型实例时,为这些模板指定实际的数据类型。类型本身可以被看作是对象的蓝图,而泛型则可以看作是类型的蓝图。

C# 支持五种泛型:类、结构、接口、委托和方法。前四者是类型,而方法是成员。

泛型的引入

考虑一个简单的整数栈实现:

class IntStack
{
    private int stackPointer = 0;
    private int[] stackArray;

    public void Push(int item) { /* ... implementation ... */ }
    public int Pop() { /* ... implementation ... */ return default(int); }
}
    

如果需要一个浮点数栈,传统做法是复制代码并修改所有 `int` 为 `float`:

class FloatStack
{
    private int stackPointer = 0;
    private float[] stackArray;

    public void Push(float item) { /* ... implementation ... */ }
    public float Pop() { /* ... implementation ... */ return default(float); }
}
    

这种方法存在明显缺点:代码冗余,维护困难,每增加一种新类型(如 `long`, `double`)都需要重复劳动。

泛型解决了这个问题:

class StackGeneric<T>
{
    private int stackPointer = 0;
    private T[] stackArray;

    public void Push(T item) { /* ... implementation ... */ }
    public T Pop() { /* ... implementation ... */ return default(T); }
}
    

在 `StackGeneric` 中,`T` 是一个类型占位符。编译器可以根据提供的具体类型(如 `int` 或 `float`)生成相应的具体类。

泛型类

创建和使用非泛型类通常只需两个步骤:声明类和实例化。泛型类本身是类型的模板,需要先由具体类型实例化,才能创建其实例。

  1. 声明泛型类型:使用类型占位符(类型参数)来声明类。
  2. 创建构造类型:为类型参数提供实际类型,生成具体的类定义,称为构造类型。
  3. 创建实例:实例化构造类型。

声明泛型类

泛型类的声明语法在普通类声明的基础上,在类名后添加一对尖括号 `<>`,其中包含用逗号分隔的类型参数。

class GenericContainer
{
    public TKey Key;
    public TValue Value;
}
    

这里的 `TKey` 和 `TValue` 就是类型参数,它们在类体内用作占位符。

创建构造类型

通过为泛型类声明中的类型参数提供具体的类型实参,可以创建构造类型。

// 使用 int 和 string 作为类型实参,创建 GenericContainer 的构造类型
GenericContainer pairInstance;
    

编译器根据提供的类型实参,生成一个具体的类定义。

类型参数与类型实参

  • 类型参数 (Type Parameter):在泛型类声明中定义的占位符,如 `TKey`, `TValue`。
  • 类型实参 (Type Argument):在创建构造类型时,提供给类型参数的实际类型,如 `int`, `string`。
// T1, T2 是类型参数
class SampleGeneric<T1, T2> { /* ... */ }

// int, float 是类型实参
SampleGeneric<int, float> myGenericInstance;
    

创建变量和实例

创建泛型类(构造类型)的变量和实例,与创建非泛型类对象类似。

// 显式指定构造类型
GenericContainer<short, int> container1 = new GenericContainer<short, int>();

// 使用 var 关键字,编译器推断类型
var container2 = new GenericContainer<short, int>();
    

变量声明和实例创建也可以分开进行:

GenericContainer<short, int> myContainerRef; // 声明引用
myContainerRef = new GenericContainer<short, int>(); // 实例化并赋值
    

泛型类与非泛型类的比较

方面 非泛型类 泛型类
代码大小 每种类型都需要一个实现,代码量大 一个模板适用于所有构造类型,代码量小
可执行文件大小 编译时会包含所有实现的版本 只包含实际使用的构造类型
编写难度 简单,因具体而明确 稍复杂,因抽象而通用
维护难度 修改需应用到所有版本,易出错 修改只需一处,易于维护

类型参数的约束

有时,泛型代码需要对类型参数施加限制,以确保它们支持某些操作。例如,比较两个泛型类型的大小:

class Comparer<T>
{
    // 错误示例:T 可能不支持 '<' 运算符
    // public static bool IsLess(T a, T b)
    // {
    //     return a < b;
    // }
}
    

编译器无法保证类型参数 `T` 支持 `<` 运算符。此时需要使用约束来限定 `T` 必须是支持该操作的类型。

where 子句

约束通过 `where` 子句来指定。每个有约束的类型参数都可以有自己的 `where` 子句,多个约束用逗号分隔。

// where 子句语法
// TypeParam: 约束1, 约束2, ...

// 示例:
class DataProcessor<TData, TComparator>
    where TData : struct         // TData 必须是值类型
    where TComparator : IComparer // TComparator 必须实现 IComparer 接口
{
    // ...
}
    

要点:

  • `where` 子句跟在类型参数列表之后。
  • 约束列表内用逗号分隔。
  • `where` 是上下文关键字。

约束的类型和顺序

约束共有五种类型:

  • 类名约束:类型参数必须是指定的类或其派生类。
  • class 约束:类型参数必须是引用类型。
  • struct 约束:类型参数必须是值类型。
  • 接口约束:类型参数必须实现指定的接口。
  • new() 约束:类型参数必须有一个无参公共构造函数(称为构造函数约束)。

在 `where` 子句中,约束的顺序有要求:

  • 最多一个主约束(类名、struct 或 class),且必须放在最前面。
  • 可以有任意数量的接口约束。
  • 构造函数约束 (`new()`) 必须放在最后。
标签: C#
返回列表

上一篇:集成企业微信实现禅道扫码登录

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。