泛型是 C# 2.0 的最强大的功能。通过泛型可以定义类型安全的数据结构,而无须使用实际的数据类型。这能够显著提高性能并得到更高质量的代码,因为您可以重用数据处理算法,而无须复制类型特定的代码。在概念上,泛型类似于 C++ 模板,但是在实现和功能方面存在明显差异。本文讨论泛型处理的问题空间、它们的实现方式、该编程模型的好处,以及独特的创新(例如,约束、一般方法和委托以及一般继承)。
您还将了解在 .NET Framework 的其他领域(例如,反射、数组、集合、序列化和远程处理)中如何利用泛型,以及如何在所提供的基本功能的基础上进行改进。
泛型问题陈述:
考虑一种普通的、提供传统 Push() 和 Pop() 方法的数据结构(例如,堆栈)。
在开发通用堆栈时,您可能愿意使用它来存储各种类型的实例。
在 C# 1.1 下,您必须使用基于 Object 的堆栈,这意味着,在该堆栈中使用的内部数据类型是难以归类的 Object,并且堆栈方法与 Object 交互:
public class Stack
{
object[] m_Items;
public void Push(object item)
{}
public object Pop()
{}
}
{
object[] m_Items;
public void Push(object item)
{}
public object Pop()
{}
}
代码块 1
显示基于 Object 的堆栈的完整实现。
因为 Object 是规范的 .NET 基类型,所以您可以使用基于 Object 的堆栈来保持任何类型的项(例如,整数):
Stack stack = new Stack();
stack.Push(1);
stack.Push(2);
int number = (int)stack.Pop();
stack.Push(1);
stack.Push(2);
int number = (int)stack.Pop();
代码块 1 是基于 Object 的堆栈
// 基于Object的堆栈
public class Stack
{
private readonly int m_size; // 该堆栈的最大索引
public int m_StackPointer = 0; // 索引
public object[] m_Items; // 该堆栈
/// <summary>
/// 默认构造函数,设置堆栈的最大索引为 100
/// </summary>
public Stack() : this(100) { }
/// <summary>
/// 该构造函数用于初始化 堆栈的索引
/// </summary>
/// <param name="size">最大的索引值</param>
public Stack(int size)
{
m_size = size;
m_Items = new object[m_size];
}
// 压栈
public void Push(object item)
{
if (m_StackPointer >= m_size)
throw new StackOverflowException();
m_Items[m_StackPointer] = item;
m_StackPointer++;
}
// 出栈
public object Pop()
{
m_StackPointer--;
if (m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
throw new InvalidOperationException("Cannot pop an empty stack");
}
}
public class Stack
{
private readonly int m_size; // 该堆栈的最大索引
public int m_StackPointer = 0; // 索引
public object[] m_Items; // 该堆栈
/// <summary>
/// 默认构造函数,设置堆栈的最大索引为 100
/// </summary>
public Stack() : this(100) { }
/// <summary>
/// 该构造函数用于初始化 堆栈的索引
/// </summary>
/// <param name="size">最大的索引值</param>
public Stack(int size)
{
m_size = size;
m_Items = new object[m_size];
}
// 压栈
public void Push(object item)
{
if (m_StackPointer >= m_size)
throw new StackOverflowException();
m_Items[m_StackPointer] = item;
m_StackPointer++;
}
// 出栈
public object Pop()
{
m_StackPointer--;
if (m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
throw new InvalidOperationException("Cannot pop an empty stack");
}
}
但是,基于 Object 的解决方案存在两个问题。
第一个问题是 性能。
在使用值类型时,必须将它们装箱以便推送和存储它们,并且在将值类型弹出堆栈时将其取消装箱。装箱和取消装箱都会根据它们自己的权限造成重大的性能损失,但是它还会增加托管堆上的压力,导致更多的垃圾收集工作,而这对于性能而言不太好。
即使是在使用引用类型而不是值类型时,仍然存在性能损失,这是因为必须从 Object 向您要与之交互的实际类型进行强制类型转换,从而造成强制类型转换开销:
Stack stack = new Stack();
stack.Push("1");
string number = (string)stack.Pop();
stack.Push("1");
string number = (string)stack.Pop();
基于 Object 的解决方案的第二个问题(通常更为严重)是 类型安全。
因为编译器允许在任何类型和 Object 之间进行强制类型转换,所以您将丢失编译时类型安全。例如,以下代码可以正确编译,但是在运行时将引发无效强制类型转换异常:
Stack stack = new Stack();
stack.Push(1);
//This compiles, but is not type safe, and will throw an exception:
string number = (string)stack.Pop();
stack.Push(1);
//This compiles, but is not type safe, and will throw an exception:
string number = (string)stack.Pop();
您可以通过提供类型特定的(因而是类型安全的)高性能堆栈来克服上述两个问题。
对于整型,可以实现并使用 IntStack:
public class IntStack
{
int[] m_Items;
public void Push(int item){}
public int Pop(){}
}
IntStack stack = new IntStack();
stack.Push(1);
int number = stack.Pop();
对于字符串,可以实现 StringStack: {
int[] m_Items;
public void Push(int item){}
public int Pop(){}
}
IntStack stack = new IntStack();
stack.Push(1);
int number = stack.Pop();
public class StringStack
{
string[] m_Items;
public void Push(string item){}
public string Pop(){}
}
StringStack stack = new StringStack();
stack.Push("1");
string number = stack.Pop();
{
string[] m_Items;
public void Push(string item){}
public string Pop(){}
}
StringStack stack = new StringStack();
stack.Push("1");
string number = stack.Pop();
等等。
遗憾的是,以这种方式解决性能和类型安全问题,会引起第三个同样严重的问题
影响工作效率:
编写类型特定的数据结构是一项乏味的、重复性的且易于出错的任务。在修复该数据结构中的缺陷时,您不能只在一个位置修复该缺陷,而必须在实质上是同一数据结构的类型特定的副本所出现的每个位置进行修复。此外,没有办法预知未知的或尚未定义的将来类型的使用情况,因此还必须保持基于 Object 的数据结构。
结果
大多数 C# 1.1 开发人员发现类型特定的数据结构不实用,并且选择使用基于 Object 的数据结构,尽管它们存在缺点。