C# - 泛型
C# - 泛型
Section titled “C# - 泛型”C# 中的泛型是什么?
Section titled “C# 中的泛型是什么?”泛型允许你设计和编写类型安全而又灵活的类、方法和接口。它们让你能够为数据类型定义一个占位符(一个“类型参数”),然后在代码使用时指定这个类型。这允许你编写一段代码,它能与任何数据类型协同工作,从而最大化代码重用、性能和类型安全性。
最常见的例子是泛型集合。你无需创建单独的 IntList、StringList 和 ProductList,而是可以使用泛型 List<T>,其中 T 是你提供的类型参数:List<int>、List<string>、List<Product>。
泛型类使用尖括号(<>)中的类型参数进行定义。这允许类针对不同的数据类型进行实例化,同时确保类内部的所有操作都是类型安全的。
示例:一个简单的泛型“包装器”类
Section titled “示例:一个简单的泛型“包装器”类”让我们创建一个可以“包装”任何类型值的简单类。
using System;
// 一个带有一个类型参数 T 的泛型类public class DataWrapper<T>{ // 内部数据是 T 类型 public T Data { get; private set; }
public DataWrapper(T data) { this.Data = data; }
public void DisplayData() { Console.WriteLine($"Data is: {Data} (Type: {typeof(T).Name})"); }}
// --- 用法 ---// 为整数创建一个包装器var intWrapper = new DataWrapper<int>(123);intWrapper.DisplayData(); // 输出: Data is: 123 (Type: Int32)
// 为字符串创建一个包装器var stringWrapper = new DataWrapper<string>("Hello, Generics!");stringWrapper.DisplayData(); // 输出: Data is: Hello, Generics! (Type: String)最佳实践:使用内置泛型集合
Section titled “最佳实践:使用内置泛型集合”虽然创建自己的泛型类很强大,但你几乎总是应该优先选择 System.Collections.Generic 命名空间中高度优化的内置泛型集合。这些集合包括:
List<T>:最常见的,一个动态大小的列表。Dictionary<TKey, TValue>:键值对的集合。Queue<T>:先进先出(FIFO)集合。Stack<T>:后进先出(LIFO)集合。HashSet<T>:唯一元素的集合。
你也可以创建泛型方法,即使它们在非泛型类中。这对于对任何类型执行操作的实用函数非常有用。
示例:一个泛型交换方法
Section titled “示例:一个泛型交换方法”这个经典的例子展示了一个可以交换任何类型两个变量的方法。
public class Utility{ public static void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; }}
// --- 用法 ---int x = 10, y = 20;Console.WriteLine($"Before: x={x}, y={y}");Utility.Swap(ref x, ref y); // 类型 T 被推断为 intConsole.WriteLine($"After: x={x}, y={y}");
string s1 = "Hello", s2 = "World";Console.WriteLine($"Before: s1={s1}, s2={s2}");Utility.Swap(ref s1, ref s2); // 类型 T 被推断为 stringConsole.WriteLine($"After: s1={s1}, s2={s2}");泛型委托 (Func 和 Action)
Section titled “泛型委托 (Func 和 Action)”在 .NET 引入内置泛型委托之前,你必须为每种不同的方法签名定义一个新的委托。现在,你几乎在所有情况下都可以使用 Func<...> 和 Action<...>。
Action<T>:表示一个带有一个或多个参数(最多 16 个)且返回void的方法。Func<T, TResult>:表示一个带有一个或多个参数并返回TResult类型值的方法(最后一个类型参数始终是返回类型)。
示例:使用 Func
Section titled “示例:使用 Func”// 这个 Func 委托表示任何接收两个 int 并返回一个 int 的方法。Func<int, int, int> operation;
// 将其指向一个 'Add' 方法operation = (a, b) => a + b; // 使用 lambda 表达式Console.WriteLine($"Sum: {operation(10, 5)}"); // 输出: Sum: 15
// 将其指向一个 'Multiply' 方法operation = (a, b) => a * b;Console.WriteLine($"Product: {operation(10, 5)}"); // 输出: Product: 50接口也可以是泛型的。这在现代软件设计中非常常见,例如在 Repository Pattern(仓储模式)中。
示例:一个泛型仓储接口
Section titled “示例:一个泛型仓储接口”这个接口定义了一个数据仓储的契约,可以与任何实体类型协同工作。
// 泛型接口public interface IRepository<T>{ T GetById(int id); IEnumerable<T> GetAll(); void Add(T entity);}
// 'Product' 类的一个具体实现public class ProductRepository : IRepository<Product>{ private readonly List<Product> _products = new();
public void Add(Product entity) => _products.Add(entity); public IEnumerable<Product> GetAll() => _products; public Product GetById(int id) => _products.FirstOrDefault(p => p.Id == id);}
// (假设 Product 类已存在)public record Product(int Id, string Name);有时你需要保证类型参数 T 具有某些能力(例如,它有一个无参数构造函数或实现了特定的接口)。你可以使用 where 关键字通过约束来强制实现这一点。
// 此方法只接受是类且具有无参数构造函数的类型 T。public T CreateInstance<T>() where T : class, new(){ return new T();}
// 此方法要求 T 必须实现 IDisposable 接口。public void ProcessAndDispose<T>(T item) where T : IDisposable{ // 现在我们可以安全地调用 Dispose(),因为约束保证了它的存在。 item.Dispose();}