我翻译这篇文章写得很好,解释得很清楚,尤其是其图文,让人思路清晰,比MS提供的SDK上请的好懂多了,反正我当年看了好几次还似懂非懂的。我还会翻译我关于C#中的堆与栈的一个系列文章(共四篇),敬请期待哦。
int i = 0;
object obj = (object) i; //装箱
obj = ((int) i) + 3; // 在堆上改变i的值
i += 1; // 在栈上改变i的值
Console.WriteLine(obj.ToString());
Console.WriteLine(i.ToString());
结果:
下面来一步一步分析这一过程
第一步:i 被放放置在栈上
第二步:i 被拷到堆上(装箱)并将 obj 放到栈上,obj 的值是在堆上的新对象的内在地址(即指针)
第三步:在堆上的对象被更新,值变为3
第四步:在栈上的对象 i 被更新,值变为1
下面是一个更复杂的例子:
class Class1
{
[STAThread]
static void Main(string[] args)
{
MyInt test = new MyInt();
test.value = 100;
test.AddOne();
aDelegate del = new aDelegate(test.AddOne);
del();
test.AddOne();
}
}
public delegate void aDelegate();
public struct MyInt
{
public int value;
public void AddOne()
{
Console.WriteLine(string.Format("Before: {0}", value.ToString()));
value += 1;
Console.WriteLine(string.Format("After : {0}", value.ToString()));
}
}
下面是输出
结果有没有让你感意外?发生了什么?让我们来分析一下它的过程。在Main()和args[]被置于栈上后,主函数开始执行:
第一步:test被置于栈上
第二步:AddOne()被置于栈上并执行,将Mint.value的值变为101;
第三步:AddOne()被从栈上移除,Myint被装箱(拷)到堆上。现在Del指向MyInt对象在堆上的版本。
第四步:装箱的对象的AddOne()被调用,将其值更新为102.
第五步:来自堆的AddOne()被移除
第六步:Myint对象在栈上的版本(即test)的AddOne()入栈,将其值变为102.
最后:主函数执行完毕,栈被清空。而留在堆上的对象则等待被垃圾回收。
下面让我们做一个改变,来提高我们的应用程序的性能,并使其结果更符合我们的意愿。如果我们将MyInt改为引用类型,我们就只需处理在堆上的一个对象并且没有装箱的过程。
public class MyInt
{
public int value;
public void AddOne()
{
Console.WriteLine(string.Format("Before: {0}", value.ToString()));
value += 1;
Console.WriteLine(string.Format("After : {0}", value.ToString()));
}
}
修改后,我们得到了我们期望的结果:
Before: 100
After : 101
Before: 101
After : 102
Before: 102
After : 103
总结
当我们在一个值类型上调用ToString()或是GetType(),会有更多的意想不到的装箱过程。因为在这种情况下,方法是从基类(System.Object)被调用,所以必需经过装箱过程后,方法才能被调用。当我们将值类型加入ArrayList()(在这种情况下它们被类型转换为System.Object)时也会发生装箱过程。你可以想象,当装箱过程大规模发生时,将会严重影响程序的性能。
希望这篇文章能让你对值类型的装箱过程,以及在通过引用如接口、委托或Arralist来处理值类型时应注意些什么有更好的理解。