我翻譯這篇文章寫得很好,解釋得很清楚,尤其是其圖文,讓人思路清晰,比MS提供的SDK上請的好懂多了,反正我當年看了好幾次還似懂非懂的。我還會翻譯我關於C#中的堆與棧的一個系列文章(共四篇),敬請期待哦。
The C# Value Type and Boxing
Matthew Cochran
儘管在.NET架構裡,我們不需要為記憶體管理以及垃圾收集操心,但我們還是應該瞭解它們,來最佳化我們的應用程式。其中之一便是,公用語言運行環境(CLR)是如何處理對實值型別的引用的。
當一個實值型別執行個體被轉換成System.Object類型或是介面時,CLR需要把實值型別轉換為一個恰當的參考型別。然後在託管堆上分配記憶體並將對象拷上去。我們之所以要瞭解它,有兩個原因:裝箱是一個非常耗費資源的過程(將整個對象從棧上拷到堆上會耗費處理器周期以及託管堆空間),我們因此(裝箱)而有了兩個在記憶體中可以有相矛盾狀態的對象。
這是一個簡單的裝箱的例子。
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來處理實值型別時應注意些什麼有更好的理解。