C# 中的泛型:使用与优势完全指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C# 中的泛型:为什么需要以及如何使用



泛型(Generics)是 C# 语言的关键特性之一,它允许创建带有参数化类型的类、方法和接口。无需为每种数据类型编写相同的代码,只需编写一次,在使用时指定类型即可。这提高了性能、类型安全性和代码的可重用性。在本文中,我们将分析为什么需要泛型,如何在实际中应用它们,并查看 .NET 中的真实示例。



为什么 C# 中需要泛型



在 C#(2.0 版本)出现泛型之前,开发人员经常使用 object 类型和 ArrayList 集合。这导致了两个问题:



  • 性能损失 — 值类型(int, double, struct)的装箱(boxing)和拆箱(unboxing)。
  • 缺少编译时类型检查 — 可能意外地将字符串放入数字列表,并仅在运行时才得到错误。


泛型解决了这两个问题。它们允许创建类型安全的数据结构,而无需额外的开销。例如,List<int> 处理 int 时无需装箱,并且编译器不允许向其中添加字符串。



泛型的基本概念



泛型类



泛型类通过在尖括号中声明类型参数来定义。类型参数是一个占位符,在创建实例时会被实际类型替换。



public class Box<T>{    private T _value;

public Box(T value) { _value = value; }

public T GetValue() => _value;}


使用方式:



var intBox = new Box<int>(42);var stringBox = new Box<string>("Hello");

Console.WriteLine(intBox.GetValue()); // 42Console.WriteLine(stringBox.GetValue());// Hello


泛型方法



方法可以拥有自己的类型参数,即使类本身不是泛型:



public class Utils{    public static void Swap<T>(ref T a, ref T b)    {        T temp = a;        a = b;        b = temp;    }}

// 使用方式int x = 1, y = 2;Utils.Swap(ref x, ref y);Console.WriteLine($"{x} {y}"); // 2 1


泛型接口



带有类型参数的接口允许创建灵活的抽象:



public interface IRepository<T>{    T GetById(int id);    void Add(T entity);}

public class UserRepository : IRepository<User>{ public User GetById(int id) => new User(); public void Add(User user) { }}


泛型中的约束(Constraints)



有时需要对可以替换的类型施加限制。为此,使用关键字 where。



主要的约束类型



  • where T : class — 仅限引用类型(string, 类)。
  • where T : struct — 仅限值类型(int, DateTime, struct)。
  • where T : new() — 类型必须具有公共的无参数构造函数。
  • where T : SomeBaseClass — 类型必须是或继承自指定的类。
  • where T : ISomeInterface — 类型必须实现该接口。


具有多个约束的示例:



public class Repository<T> where T : class, IEntity, new(){    public T Create()    {        return new T(); // 由于 new() 约束而允许    }}


泛型中的协变和逆变



从 C# 4.0 开始,可以使用关键字 in 和 out 来控制泛型接口的变体性。



  • 协变(out) — 允许使用

博客

书籍推荐