云计算百科
云计算领域专业知识百科平台

C# 泛型:从基础到高级应用

1. 什么是泛型?

泛型(Generics)是 C# 2.0 引入的核心特性,它允许在定义类、接口、方法时使用类型参数,从而创建可重用、类型安全且高性能的代码组件。简单来说,泛型让你可以编写一个能处理多种数据类型的代码模板,而无需为每种类型重复编写相似的代码。

在没有泛型之前,我们通常使用 object 类型来实现通用容器(如 ArrayList),但这会导致:

  • 类型不安全:编译时无法检查类型,运行时可能抛出 InvalidCastException。
  • 性能损失:值类型需要频繁装箱(boxing)和拆箱(unboxing)。
  • 代码冗余:为每种类型编写几乎相同的类或方法。

泛型完美解决了这些问题。

2. 泛型的基本语法

2.1 泛型类

在类名后使用尖括号 <T> 声明类型参数:

// 定义一个简单的泛型容器类
public class GenericList<T>
{
private T[] items;
private int count = 0;

public GenericList(int capacity)
{
items = new T[capacity];
}

public void Add(T item)
{
if (count < items.Length)
{
items[count] = item;
count++;
}
}

public T Get(int index)
{
if (index >= 0 && index < count)
return items[index];
throw new IndexOutOfRangeException();
}
}

// 使用示例
GenericList<int> intList = new GenericList<int>(10);
intList.Add(42);
int value = intList.Get(0); // 类型安全,无需强制转换

GenericList<string> stringList = new GenericList<string>(5);
stringList.Add("Hello");
string text = stringList.Get(0);

2.2 泛型方法

方法也可以独立使用泛型,即使所在类不是泛型类:

public class Utility
{
// 泛型方法:交换两个变量的值
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}

// 使用示例
public static void Demo()
{
int x = 10, y = 20;
Swap(ref x, ref y); // 编译器推断 T 为 int

string s1 = "first", s2 = "second";
Swap(ref s1, ref s2); // T 为 string
}
}

2.3 泛型接口

接口同样可以定义类型参数:

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

// 实现泛型接口
public class UserRepository : IRepository<User>
{
public User GetById(int id) { /* 实现 */ }
public void Add(User entity) { /* 实现 */ }
public void Update(User entity) { /* 实现 */ }
public void Delete(int id) { /* 实现 */ }
}

3. 类型约束(Constraints)

默认情况下,泛型类型参数 T 可以是任何类型。但有时我们需要限制 T 必须满足某些条件,这时可以使用类型约束:

// T 必须是引用类型(类、接口、委托、数组)
public class ReferenceTypeClass<T> where T : class
{
public T Instance { get; set; }
}

// T 必须是值类型(结构体、枚举),且不能是可空类型
public struct ValueTypeStruct<T> where T : struct
{
public T Value { get; set; }
}

// T 必须具有无参构造函数
public class Creatable<T> where T : new()
{
public T CreateInstance()
{
return new T();
}
}

// T 必须继承自指定基类或实现指定接口
public class Processor<T> where T : IComparable, IFormattable
{
public int Compare(T a, T b)
{
return a.CompareTo(b);
}
}

// 多重约束
public class ComplexGeneric<T> where T : class, IComparable<T>, new()
{
// T 必须是引用类型、实现 IComparable<T>、且有默认构造函数
}

4. 泛型集合(System.Collections.Generic)

.NET Framework 提供了丰富的内置泛型集合类,这是泛型最常用的场景:

using System.Collections.Generic;

// List<T>:动态数组
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Add(6);

// Dictionary<TKey, TValue>:键值对集合
Dictionary<string, int> ages = new Dictionary<string, int>
{
["Alice"] = 25,
["Bob"] = 30
};
int bobAge = ages["Bob"];

// Queue<T>:先进先出队列
Queue<string> queue = new Queue<string>();
queue.Enqueue("first");
queue.Enqueue("second");
string firstItem = queue.Dequeue();

// Stack<T>:后进先出栈
Stack<double> stack = new Stack<double>();
stack.Push(3.14);
stack.Push(2.71);
double top = stack.Pop();

// HashSet<T>:不包含重复元素的集合
HashSet<string> uniqueNames = new HashSet<string>
{
"John", "Jane", "John" // 第二个 "John" 不会被添加
};

5. 协变与逆变(Covariance & Contravariance)

C# 4.0 引入了泛型接口和委托的协变与逆变,提供了更灵活的类型转换:

5.1 协变(out 关键字)

允许使用派生程度更大的类型(更具体的类型)。只能用于输出位置(返回值):

// IEnumerable<T> 是协变的:IEnumerable<out T>
IEnumerable<string> strings = new List<string> { "a", "b", "c" };
// 可以将 IEnumerable<string> 赋值给 IEnumerable<object>
IEnumerable<object> objects = strings; // 协变,因为 string 派生自 object

// 自定义协变接口
public interface IProducer<out T>
{
T Produce();
}

public class StringProducer : IProducer<string>
{
public string Produce() => "Hello";
}

IProducer<string> stringProducer = new StringProducer();
IProducer<object> objectProducer = stringProducer; // 协变允许

5.2 逆变(in 关键字)

允许使用派生程度更小的类型(更通用的类型)。只能用于输入位置(参数):

// IComparer<T> 是逆变的:IComparer<in T>
IComparer<object> objectComparer = Comparer<object>.Default;
// 可以将 IComparer<object> 赋值给 IComparer<string>
IComparer<string> stringComparer = objectComparer; // 逆变,因为 object 是 string 的基类

// 自定义逆变接口
public interface IConsumer<in T>
{
void Consume(T item);
}

public class ObjectConsumer : IConsumer<object>
{
public void Consume(object item) { }
}

IConsumer<object> objectConsumer = new ObjectConsumer();
IConsumer<string> stringConsumer = objectConsumer; // 逆变允许

6. 泛型委托

委托也可以使用泛型,.NET 内置了常用的泛型委托:

// Func<TResult>:无参数,返回 TResult
Func<int> getRandom = () => new Random().Next(100);
int randomValue = getRandom();

// Func<T, TResult>:一个参数,返回 TResult
Func<string, int> stringLength = s => s.Length;
int length = stringLength("Hello");

// Action<T>:一个参数,无返回值
Action<string> logger = message => Console.WriteLine(message);
logger("Log message");

// Predicate<T>:一个参数,返回 bool
Predicate<int> isEven = n => n % 2 == 0;
bool result = isEven(4);

// 自定义泛型委托
public delegate T Transformer<T>(T input);
public delegate TResult Converter<TInput, TResult>(TInput input);

Transformer<int> square = x => x * x;
int squared = square(5); // 25

7. 泛型的最佳实践

  • 优先使用泛型集合:总是使用 List<T>、Dictionary<TKey, TValue> 等泛型集合,避免使用 ArrayList、Hashtable 等非泛型集合。
  • 合理使用约束:只在必要时添加约束,过多的约束会限制泛型的灵活性。
  • 考虑可空性:C# 8.0 的可空引用类型与泛型结合时,使用 T? 语法或 default 关键字处理可能为 null 的情况。
  • 避免过度泛化:如果类型参数只在少数地方使用,考虑使用重载方法而非泛型。
  • 性能考量:泛型在编译时生成特定类型的代码,避免了装箱拆箱,性能接近硬编码类型。

8. 总结

C# 泛型是现代 .NET 开发的基石,它提供了:

  • 类型安全:编译时类型检查,减少运行时错误。
  • 代码重用:一套代码处理多种数据类型。
  • 性能优化:避免装箱拆箱,生成高效的特化代码。
  • 丰富的生态系统:LINQ、集合类、异步编程等都深度依赖泛型。
  • 掌握泛型不仅能让你的代码更加健壮和高效,也是理解 .NET 高级特性(如 LINQ、异步流、依赖注入容器)的必要前提。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » C# 泛型:从基础到高级应用
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!