C# 中的泛型:为什么需要以及如何在实践中使用
泛型(Generics)是 C# 语言的关键特性之一,它允许编写灵活、可复用且类型安全的代码。在本文中,我们将探讨为什么需要泛型,它们如何在底层工作,并展示在实际项目中应用它们的实用示例。
什么是泛型以及为什么需要它们?
想象一下,你需要实现一个交换两个值的方法。如果没有泛型,你必须为每种数据类型编写单独的版本:
// 没有泛型——代码重复void SwapInt(ref int a, ref int b) { int temp = a; a = b; b = temp; }void SwapString(ref string a, ref string b) { string temp = a; a = b; b = temp; }// ... 每个类型都要这样写使用泛型,这个问题可以优雅地解决:
// 使用泛型——一个方法适用于所有类型void Swap<T>(ref T a, ref T b){ T temp = a; a = b; b = temp;}
// 使用方式int x = 1, y = 2;Swap(ref x, ref y); // T = int
string s1 = "Hello", s2 = "World";Swap(ref s1, ref s2); // T = string泛型的主要优势:
- 类型安全——类型错误在编译阶段即可捕获
- 性能——避免值类型的装箱/拆箱
- 代码复用——一个类/方法可处理任何类型
- 可读性——代码更清晰、更简洁
泛型类与结构体
泛型最常见的应用示例是集合。无需为每种类型编写自己的类,我们可以创建泛型类:
public class MyList<T>{ private T[] _items; private int _count;
public MyList(int capacity = 4) { _items = new T[capacity]; _count = 0; }
public void Add(T item) { if (_count == _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count++] = item; }
public T Get(int index) { if (index < 0 || index >= _count) throw new IndexOutOfRangeException(); return _items[index]; }
public int Count => _count;}
// 使用方式var numbers = new MyList<int>();numbers.Add(10);numbers.Add(20);
var names = new MyList<string>();names.Add("Alice");请注意:创建实例时,我们在尖括号 <T> 中指定具体类型。编译器会将此类型替换到所有使用 T 的位置。
泛型方法与约束(constraints)
有时需要对类型 T 施加限制。例如,确保类型实现了特定接口或具有无参构造函数:
// 约束:T 必须实现 IComparable<T>public T GetMax<T>(T a, T b) where T : IComparable<T>{ return a.CompareTo(b) > 0 ? a : b;}
// 约束:T 必须具有无参构造函数public class Factory<T> where T : new(){ public T Create() { return new T(); }}
// 约束:T 必须是值类型(struct)public struct Nullable<T> where T : struct{ private T _value; private bool _hasValue;
public Nullable(T value) { _value = value; _hasValue = true; }
public T Value => _hasValue ? _value : throw new InvalidOperationException(); public bool HasValue => _hasValue;}主要的约束类型:
where T : class——引用类型where T : struct——值类型博客
书籍推荐
推荐阅读