本文將介紹以下內容:
類型的基本概念
值類型深入
引用類型深入
值類型與引用類型的比較及應用
1.引言
值類型與引用類型的話題經過了兩個回合([第八回:品味類型---值類型與引用類型(上)-內存有理]和[第九回:品味類型---值類型與引用類型(中)-規則無邊])的討論和切磋,我們就基本的理解層面來說已經差不多了,但是對這一部分的進一步把握和更深刻的理解還要繼續和深化,因為我自己就在兩篇發布之際,我就得到裝配腦袋兄的不倦指導,之後又查閱了很多的資料發現類型在.NET或者說語言基礎中何其重要的內涵和深度,因此關於這個話題的討論還沒有停止,以後我將繼續分享自己的所得與所感。
不過作為一個階段,本文將值類型和引用類型的討論從應用示例角度來進一步做以延伸,可以看作是對前兩回的補充性探討。我們從類型定義、實例創建、參數傳遞、類型判等、垃圾回收等幾個方面來簡要的對上兩回的內容做以剖析,並以一定的IL語言和內存機制來說明,期望進一步加深我們的理解和分析。
2.以代碼剖析
下面,我們以一個經典的值類型和引用類型對比的示例來剖析,其區別和實質。在剖析的過程中,我們主要以執行分析(主要是代碼注釋)、內存分析(主要是圖例說明)和IL分析(主要是IL代碼簡析)三個方面來逐知識點解析,最後再做以總結描述,這樣就可以有更深的理解。
2.1 類型定義
定義簡單的值類型MyStruct和引用類型MyClass,在後面的示例中將逐漸完善,完整的代碼可以點擊下載[類型示例代碼]。我們的討論現在開始,
代碼演示
// 01 定義值類型
public struct MyStruct
{
private int _myNo;
public int MyNo
{
get { return _myNo; }
set { _myNo = value; }
}
public MyStruct(int myNo)
{
_myNo = myNo;
}
public void ShowNo()
{
Console.WriteLine(_myNo);
}
}
// 02 定義引用類型
public class MyClass
{
private int _myNo;
public int MyNo
{
get { return _myNo; }
set { _myNo = value; }
}
public MyClass()
{
_myNo = 0;
}
public MyClass(int myNo)
{
_myNo = myNo;
}
public void ShowNo()
{
Console.WriteLine(_myNo);
}
}
IL分析
分析IL代碼可知,靜態方法.ctor用來表示實現構造方法的定義,其中該段IL代碼表示將0賦給字段_myNo。
2.2 創建實例、初始化及賦值
接下來,我們完成實例創建和初始化,和簡單的賦值操作,然後在內存和IL分析中發現其實質。
代碼演示
創建實例、初始化及賦值
public static void Main(string[] args)
{
//內存分配於線程的堆棧上
//創建了值為等價"0"的實例
MyStruct myStruct = new MyStruct();
//在線程的堆棧上創建了引用,但未指向任何實例
MyClass myClass;
//內存分配於托管堆上
myClass = new MyClass();
//在堆棧上修改成員
myStruct.MyNo = 1;
//將指針指向托管堆
myClass.MyNo = 2;
myStruct.ShowNo();
myClass.ShowNo();
//在堆棧上新建內存,並執行成員拷貝
MyStruct myStruct2 = myStruct;
//拷貝引用地址
MyClass myClass2 = myClass;
//在堆棧上修改myStruct成員
myStruct.MyNo = 3;
//在托管堆上修改成員
myClass.MyNo = 4;
myStruct.ShowNo();
myClass.ShowNo();
myStruct2.ShowNo();
myClass2.ShowNo();
}
內存實況
首先是值類型和引用類型的定義,這是一切面向對象的開始,
然後是初始化過程,
簡單的賦值和拷貝,是最基本的內存操作,不妨看看,
2.3 參數傳遞
代碼演示
參數傳遞
// 04 ref和out
public class RefAndOut
{
public static void Main()
{
//必須進行初始化,才能使用ref方式傳遞
int x = 10;
ValueWithRef(ref x);
Console.WriteLine(x);
//使用out方式傳遞,不必初始化
int y;
ValueWithOut(out y);
Console.WriteLine(y);
object oRef = new object();
RefWithRef(ref oRef);
Console.WriteLine(oRef.ToString());
object owith;
RefWithOut(out owith);
Console.WriteLine(owith.ToString());
}
static void ValueWithRef(ref int i)
{
i = 100;
Console.WriteLine(i.ToString());
}
static void ValueWithOut(out int i)
{
i = 200;
Console.WriteLine(i.ToString());
}
static void RefWithRef(ref object o)
{
o = new MyStruct();
Console.WriteLine(o.ToString());
}
static void RefWithOut(out object o)
{
o = new String('a', 10);
Console.WriteLine(o.ToString());
}
}
不必多說,就是一個簡要闡釋,對於參數的傳遞作者將計劃以更多的筆墨來在後面的系列中做以澄清和深入。
2.4 類型轉換
類型轉換的演示,包括很多個方面,在此我們只以自定義類型轉換為例來做以說明,更詳細的類型轉換可以參考[第九回:品味類型---值類型與引用類型(中)-規則無邊]的[再論類型轉換部分]。
代碼演示
首先是值類型的自定義類型轉換,
public struct MyStruct
{
// 01.2 自定義類型轉:整形->MyStruct型
static public explicit operator MyStruct(int myNo)
{
return new MyStruct(myNo);
}
}
然後是引用類型的自定義類型轉換,
public class MyClass
{
// 02.2 自定義類型轉換:MyClass->string型
static public implicit operator string(MyClass mc)
{
return mc.ToString();
}
public override string ToString()
{
return _myNo.ToString();
}
}
最後,我們對自定義的類型做以測試,
public static void Main(string[] args)
{
#region 03.類型轉換
MyStruct MyNum;
int i = 100;
MyNum = (MyStruct)i;
Console.WriteLine("整形顯式轉換為MyStruct型---");
Console.WriteLine(i);
MyClass MyCls = new MyClass(200);
string str = MyCls;
Console.WriteLine("MyClass型隱式轉換為string型---");
Console.WriteLine(str);
#endregion
}
2.5 類型判等
類型判等主要包括:ReferenceEquals()、Equals()虛方法和靜態方法、==操作符等方面,同時注意在值類型和引用類型判等時的不同之處,可以參考[第九回:品味類型---值類型與引用類型(中)-規則無邊]的[4.再論類型判等]的簡述。
代碼演示
// 01 定義值類型
public struct MyStruct
{
// 01.1 值類型的類型判等
public override bool Equals(object obj)
{
return base.Equals(obj);
}
}
public class MyClass
{
// 02.1 引用類型的類型判等
public override bool Equals(object obj)
{
return base.Equals(obj);
}
}
public static void Main(string[] args)
{
#region 05 類型判等
Console.WriteLine("類型判等---");
// 05.1 ReferenceEquals判等
//值類型總是返回false,經過兩次裝箱的myStruct不可能指向同一地址
Console.WriteLine(ReferenceEquals(myStruct, myStruct));
//同一引用類型對象,將指向同樣的內存地址
Console.WriteLine(ReferenceEquals(myClass, myClass));
//RefenceEquals認為null等於null,因此返回true
Console.WriteLine(ReferenceEquals(null, null));
// 05.2 Equals判等
//重載的值類型判等方法,成員大小不同
Console.WriteLine(myStruct.Equals(myStruct2)) ;
//重載的引用類型判等方法,指向引用相同
Console.WriteLine(myClass.Equals(myClass2));
#endregion
}
2.6 垃圾回收
首先,垃圾回收機制,絕對不是三言兩語就能交代清楚,分析明白的。因此,本示例只是從最簡單的說明出發,對垃圾回收機制做以簡單的分析,目的是有始有終的交代實例由創建到消亡的全過程。
代碼演示
public static void Main(string[] args)
{
#region 06 垃圾回收的簡單闡釋
//實例定義及初始化
MyClass mc1 = new MyClass();
//聲明但不實體化
MyClass mc2;
//拷貝引用,mc2和mc1指向同一托管地址
mc2 = mc1;
//定義另一實例,並完成初始化
MyClass mc3 = new MyClass();
//引用拷貝,mc1、mc2指向了新的托管地址
//那麼原來的地址成為GC回收的對象,在
mc1 = mc3;
mc2 = mc3;
#endregion
}
內存實況
GC執行時,會遍歷所有的托管堆對象,按照一定的遞歸遍歷算法找出所有的可達對象和不可訪問對象,顯然本示例中的托管堆A對象沒有被任何引用訪問,屬於不可訪問對象,將被列入執行垃圾收集的目標。對象由newobj指令產生,到被GC回收是一個復雜的過程,我們期望在系列的後期對此做以深入淺出的理解。
2.7 總結陳述
這些示例主要從從基礎的方向入手來剖析前前兩回中的探討,不求能夠全面而深邃,但求能夠一點而及面的展開,技術的魅力正在於千變萬化,技術追求者的力求卻是從變化中尋求不變,不然我們實質太累了,我想這就是好方法,本系列希望的就是提供一個入口,打開一個方法。示例的詳細分析可以下載[類型示例代碼],簡單的分析希望能帶來絲絲惬意。
3.結論
值類型和引用類型,要說的,要做的,還有很多。此篇只是一個階段,更多的深入和探討我相信還在繼續,同時廣泛的關注技術力量的成長,是每個人應該進取的空間和道路。
品味類型,為應用之路開辟技術基礎。
品味類型,繼續探討還會更多精彩。
來源:http://www.cnblogs.com/anytao/archive/2007/06/18/must_net_10.html