我知道很多人都討論過這個問題, 多我一個不多, 少我一個不少.
最近有人又問到這個問題, 所以想再說說. 萬丈高樓平地起, 地基很重要. 懂了的人不要嫌羅嗦, 歡迎補充或者糾正.
從起源/定義說起
Struct 從C 的時代就已經有了(向丹尼斯.裡奇致敬), 它是Structure 的縮寫 -- 就是結構的意思. 它是一種最初級的資料結構, 它包含一到多個相同類型或不同類型的值或者變數. 它就像是一個儲存資料的"包".
Class(類) 是有了物件導向概念之後才有的, 它"是建立對象的藍圖,描述了所建立的對象共同的屬性和方法".
從它們被創立的用途可以看出Class 比Struct 負擔了更大的使命.
類從出生的那天就被賦予了一個偉大的使命: 類比真實世界的行為, 擁有繼承和多態兩種利器.
Struct 到了C#這個領域中有了一個進化, 它甚至可以實現介面(當然,這個也是C時代沒有的東西), 在本文中會有一個簡單的介紹.
實值型別 VS 參考型別在.net 世界中, System.Object "支援 .NET Framework 類階層中的所有類,並為衍生類別提供低層級服務。 這是 .NET Framework 中所有類的最終基類;它是類型階層的根。" 從Object再衍生出所有類、結構、枚舉和委託。看看下面這個圖對net中兩大類型的描述, -- 當然還有一種"指標類型",在這裡我們不予討論.
Struct 是屬於實值型別這個陣容, 而所有class 都是參考型別.
這意味著什麼?
當我們使用等號"賦值"時, 對於Struct而言就是將同樣的值複製給另一個變數; 而對於Class而言就只是將它們的名字指向同一個對象.
看下面的執行個體:
class FooClass{ public int FooValue;}struct FooStruct{ public int FooValue;}class Program{ static void Main(string[] args) { FooClass classObj = new FooClass(); classObj.FooValue = 0; FooClass classObj2 = classObj; classObj2.FooValue = 1; FooStruct structObj = new FooStruct(); structObj.FooValue = 0; FooStruct structObj2 = structObj; structObj2.FooValue = 1; }}
再最後設個斷點, 運行後取值: structObj2 在初始時,
會建立一個全新的副本, 然後擷取structObj 的所有數值;classObj2 在初始時, 只是指向了classObj. 這就是實值型別和參考型別的一個區別. 當修改classObj 或者 classObj2 時, 修改的是同一部分記憶體.Struct實現介面
前面提到了Struct可以實現介面, 下面我們引用一個執行個體:
interface IPromotion{ void promote();}struct Employee : IPromotion{ public string Name; public int JobGrade; public void promote() { JobGrade++; } public Employee(string name, int jobGrade) { this.Name = name; this.JobGrade = jobGrade; } public override string ToString() { return string.Format("{0} ({1})", Name, JobGrade); }}class Program{ static void Main(string[] args) { Employee employee = new Employee("Cool Guy", 65); IPromotion p = employee; Console.WriteLine(employee); p.promote(); Console.WriteLine(employee); }}
一旦增加了這個功能, 我們就可以在應用Struct之前, 給予其一些介面的定義. 使一類的struct 在形式上保持一致. 不同的用途
使用中在struct 和 class 兩者間該選誰?
我們在編程中要實現某種資料結構時, 絕大部分情況下我們會選擇class -- 因為它的強大和特定的使命. 但是當我們要傳遞或儲存一些小資料結構時,可以考慮struct.
Net framework 下有很多已經定義好了的struct例如:
System.Drawing.Rectangle
System.Drawing.Color
System.Drawing.Point
使用時要記住struct的特性.
那麼我再補充幾點吧:
初始化方面:
實值型別分配在堆棧上,變數本身包含了執行個體所有欄位。
參考型別分配在託管堆上,被分配在託管堆上的對象都有一些與之關聯的額外成員要被初始化。分配完後返回對象位於託管堆的地址。
記憶體釋放方面:
實值型別沒有分配在託管堆,所以不受GC控制。一旦實值型別不被引用,為它分配的儲存空間就會立即釋放。實值型別也有Finalize方法,但被回收時,CLR不會調用該方法。
參考型別受GC控制。
繼承方面:
不能把實值型別當作基類,所以實值型別裡不要有虛方法,也沒有抽象方法,所有的方法都隱含有sealed修飾符。
用途方面:
實值型別不要頻繁用於方法的參數傳遞。因為參數以傳值的方式傳遞,導致實值型別的欄位頻繁被拷貝。
實值型別不要作為方法傳回值頻繁返回。原因同上。
實值型別不要頻繁用於ArrayList,Hashtable類。因為一不小心會頻繁裝箱。
實值型別的裝箱與拆箱可以講很多。
就舉一個例子,什麼時候裝箱比較好。
int a=5;
Console.WriteLine("{0},{1},{2}",a,a,a);//反例
Object o=a;//裝箱
Console.WriteLine("{0},{1},{2}",o,o,o);