C# 泛型实例详解:类型约束、方法与性能优化

林舟
林舟
发布时间:2026-10-14| 2人浏览过
原创

C# 泛型允许在不损失类型安全的前提下编写可重用的算法。本文将通过具体的冒泡排序示例,详细演示如何在类和方法中使用泛型,以及如何利用泛型约束来扩展算法的适用范围。同时,文章对比了泛型集合与非泛型集合(如 ArrayList)的差异,揭示泛型在避免内存装箱/拆箱开销和提前发现编译错误方面的核心优势。

目标与准备

在处理多种不同数据类型时,如果业务逻辑相似,使用泛型可以避免重复编写代码。例如,我们需要编写一个通用的数组“冒泡排序”算法,使其能够处理 int[]、double[] 甚至自定义对象的数组。

为了让算法能对元素进行比较并排序,被操作的类型必须实现 IComparable 接口。这是应用 C# 泛型的一个典型前置条件。

针对类的泛型实现

要在类层面使用泛型,需要将类型参数声明在类名之后。下面以实现对整型数组排序为例,展示完整的逻辑。

第1步:定义泛型类及其方法

目的:创建一个能按大小调整任意实现了 IComparable 接口的数组的工具类。

代码逻辑:

class Program
{
    static void Main(string[] args)
    {
        // 调用针对 int 类型的泛型帮助器
        SortHelper isorter = new SortHelper();
        int[] iarray = {8, 7, 1, 2, 12};
        
        // 执行排序
        isorter.BubbleSort(iarray);
        
        foreach (int item in iarray)
        {
            Console.Write(item + ", ");
        }
        Console.ReadKey();
    }
}

// 泛型类定义:T 为类型参数,where T : IComparable 为约束
class SortHelper where T : IComparable
{
    public void BubbleSort(T[] array) 
    {
        int length = array.Length;
        for (int i = 0; i <= length - 2; i++)
        {
            for (int j = length - 1; j >= 1; j--)
            {
                // 利用 IComparable 接口的 CompareTo 方法比较
                if (array[j].CompareTo(array[j-1]) < 0)
                {
                    T temp = array[j];
                    array[j] = array[j - 1];
                    array[j - 1] = temp;
                }
            }
        }
    }
}

运行该段代码后,控制台会输出排序后的整数序列:

第2步:理解泛型约束机制

在上述代码中,where T : IComparable 是关键。它强制要求传入泛型的任何类型都必须具备 IComparable 接口的实现能力。

除了 IComparable,常见的约束还包括:
● where T : class:限制 T 必须是引用类型。
● where T : struct:限制 T 必须是值类型。
● where T : new():要求 T 必须包含一个无参构造函数。

这些约束可以在编译器层面拦截不符合要求的类型使用,从而提高代码的安全性。

第3步:验证内置类型的兼容性

在 .NET 框架中,基础数据类型(如 int、byte)都已实现了 IComparable 接口,因此它们天然适用于上述泛型排序方法。

自定义类实现冒泡算法

为了让泛型工具更具实用性,通常需要对用户自定义对象进行排序。由于自定义类默认不具备比较功能,因此需要手动实现 IComparable 接口。

第4步:构建带属性的自定义类

入口:新建一个表示书籍信息的类 Book。
动作:让 Book 继承 IComparable,并在 CompareTo 方法中指定比较依据(例如按价格高低排序)。
参数:属性 Price(价格)、Title(书名)。

public class Book : IComparable
{
    private int price;
    private string title;

    public Book(int price, string title)
    {
        this.price = price;
        this.title = title;
    }

    public int Price
    {
        get { return this.price; }
    }

    public string Title
    {
        get { return this.title; }
    }

    // 实现 IComparable 接口的方法
    // 这里约定按价格升序排列
    public int CompareTo(object obj)
    {
        Book book = (Book)obj;
        return this.Price.CompareTo(book.Price);
    }
}

第5步:调用泛型类对自定义对象排序

操作:在主程序中创建对象数组并传递进 SortHelper。
结果:数组将严格按照 CompareTo 定义的逻辑进行交换。

static void Main(string[] args)
{
    // 准备数据
    Book[] bookArray = new Book[2];
    Book book1 = new Book(100, "书一");
    Book book2 = new Book(80, "书二");
    bookArray[0] = book1;
    bookArray[1] = book2;

    Console.WriteLine("冒泡之前:");
    foreach (Book b in bookArray)
    {
        Console.WriteLine("书名:{0},价格:{1}", b.Title, b.Price);
    }

    // 复用之前的泛型类进行排序
    SortHelper sorter = new SortHelper();
    sorter.BubbleSort(bookArray); 

    Console.WriteLine("冒泡之后:");
    foreach (Book b in bookArray)
    {
        Console.WriteLine("书名:{0},价格:{1}", b.Title, b.Price);
    }
    Console.ReadKey();
}

程序执行前后的状态对比如下:

针对方法的泛型

有时候我们不需要在类级别定义泛型,只需要某个特定的方法具有通用性。此时可以采用“方法泛型”。

第6步:定义泛型方法

区别:泛型参数 T 出现在方法名的尖括号内,而不是类名处。

public class MethodSortHelper
{
    public void BubbleSort(T[] array) where T : IComparable
    {
        int length = array.Length;
        for (int i = 0; i <= length - 2; i++)
        {
            for (int j = length - 1; j >= 1; j--)
            {
                if (array[j].CompareTo(array[j - 1]) < 0)
                {
                    T temp = array[j];
                    array[j] = array[j - 1];
                    array[j - 1] = temp;
                }
            }
        }
    }
}

第7步:隐式推断与显式调用

当调用泛型方法时,可以显式指定类型,也可以由编译器自动推断:

MethodSortHelper sorter = new MethodSortHelper();
sorter.BubbleSort(bookArray); // 显式指定类型

// 或者这样写,编译器会根据传入的参数 bookArray 自动推断 T 就是 Book
sorter.BubbleSort(bookArray); 

这种推断机制简化了 API 的使用体验。

泛型的其它核心优点

泛型不仅仅为了代码整洁,它在底层性能和系统稳定性上有巨大贡献。

避免隐式装箱和拆箱

在泛型出现之前,开发者常使用 ArrayList 存储混合类型,但这会带来显著的性能损耗。ArrayList.Add() 接收的是 object 类型,存入数值时必须进行“装箱”(Boxing),取出时必须进行“拆箱”(Unboxing)。

ArrayList list = new ArrayList();
for(int i = 0; i < 3; i++)
{
  list.Add(i); // 发生隐式装箱
}
for(int i = 0; i < 3; i++)
{
  int value = (int)list[i]; // 发生隐式拆箱
  Console.WriteLine(value);
}

使用 List<int> 则完全避免了这些中间过程:

List list = new List();
for(int i = 0; i < 3; i++)
{
  list.Add(i); // 直接压栈,零额外开销
}

能在编译期间及时发现错误

不使用泛型的代码往往将类型错误推迟到运行时暴露:

ArrayList list = new ArrayList();
int i = 100;
list.Add(i);
string value = (string)list[0]; // 运行时抛出 InvalidCastException

使用泛型后,这类逻辑错误会在编写代码时就因为类型不匹配而被 IDE 或编译器标记出来:

List list = new List();
list.Add(100);
string value = (string)list[0]; // 编译期报错,明确禁止此类转换

泛型进阶技巧

为了使泛型定义更加简洁,可以采用别名机制。

在当前文件中给泛型取别名

通过 using 指令可以直接重命名复杂的泛型类:

using IntList = System.Collections.Generic.List<int>;
IntList list = new IntList();
list.Add(1); // 等同于上面代码

在不同文件中使用泛型别名

如果需要跨文件重用某个特定的泛型组合,可以将其定义为派生自泛型的专用类:

public class IntList : List<int> {}

// 在其它地方直接使用 IntList,无需每次都写 List<int>
以上就是 C# 泛型从基本用法到性能优化的详细内容,更多关于 C# 编程进阶的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。