非泛型集合的使用实例
假设我们创建了非泛型的System.Collections.ArrayList来保存数值(分配在栈上的)数据,查询ArrayList成员,就会发现它们所操作的是System.Object数据:
public class ArrayList:...
{
...
public virtual int Add(object value);
public virtual void Insert(int index,object value);
public virtual void Remove(object obj);
public virtual object this[int index] { get;set;}
}
ArrayList所操作的是object,这表示数据分配在堆上,然而下面的代码不但可以通过编译,而且执行时也不会抛出错误:
static void WorkWithArrayList()
{
//在传递给需要object的方法时,值类型会自动装箱
ArrayList myInts = new ArrayList();
myInts.Add(10);
myInts.Add(20);
myInts.Add(35);
}
尽管把数字数据(值类型)直接传入要求object的方法,但运行时会自动将栈数据进行装箱。
然后,如果希望从ArrayList中获取项,则必须使用转换操作,将堆分配的对象拆箱成栈分配的整数。
static void WorkWithArrayList()
{
//在传递给需要object的方法时,值类型会自动装箱
ArrayList myInts = new ArrayList();
myInts.Add(10);
myInts.Add(20);
myInts.Add(35);
//当将object转换回栈数据时,会发生拆箱
int i = (int)myInts[0];//注意,ArrayList的索引器返回System.Object,不是System.Int32;
//由于WriteLine()要求object类型,因此再次发生装箱操作
Console.WriteLine("Value of your int:{0}",i);
}
装箱/拆箱带来的问题
装箱和拆箱带来的堆/栈内存转移会导致性能问题,并且也缺乏类型安全。
类型安全问题
其中对于类型安全问题,因为拆箱必须回到合适的数据类型,如果拆箱为不正确的变量将抛出异常。为了确保安全,你需要将每个拆箱操作都包裹在try/catch逻辑中,这样工作量大并且也带来了性能问题。
static void SimpleBoxUnboxOperation()
{
//创建一个ValueType(int)变量
int myInt = 25;
//将int装箱为object引用
object boxedInt = myInt;
//拆箱为错误的数据类型将触发运行时异常
try //为保证类型安全,大量使用try/catch将导致性能问题
{
long unboxedInt = (long)boxedInt;
}
catch(InvalidCastException ex)
{
Console.WriteLine(ex.Message);
}
}
性能问题
除了try/catch可能带来的性能问题,思考一下在装箱和拆箱一个整数时发生的步骤,具体如下:
1. 必须在托管堆上分配一个新对象
2. 基于栈数据的值必须被转移到新分配的内存位置
3. 在拆箱时,保存在堆对象中的值必须转移回栈
4. 堆上无用的对象(最后)会被回收
理想情况下,我们应该可以在没有任何性能问题的容器中操作栈数据,并且在获取数据时也不必使用try/catch作用域(这正是泛型所实现的)
参考《精通C#(第六版)》章节9.2