C# 泛型实例详解:类型约束、方法与性能优化
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# 编程进阶的资料请关注本站其它相关文章!本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













