C/C++程序員或多或少都有使用struct的經歷,在C++中struct和class的區別不大,除了默認成員的可訪問性,這點在C#中則大相徑庭。本文將力圖說明C#中struct和class的區別以及如何正確的使用struct。java
衆所周知,在java中並無struct的概念,那麼C#爲什麼引入struct呢?最基本緣由是能夠建立值類型的類型,使在託管環境中有更好的性能。程序員
區別於java,C#有值類型和引用類型的概念(java只有引用類型)。引用類型的實例分配在堆上,當對象沒有被引用時被垃圾回收器回收;值類型的實例分配在棧上,當離開其做用域後內存被回收。值類型自己存儲的是值自己,引用類型存儲的是引用,C#語言提供的原始類型除了string類型其它都是值類型。函數
在C#中struct是值類型,class是引用類型,能夠經過enum和struct關鍵字建立值類型的對象。使用值類型能夠減小託管堆上對象的數量,從而減小垃圾回收器(GC)的負擔,提升性能,值類型也有明顯的缺點:經過值類型傳遞較大對象的開銷比較昂貴、裝箱和拆箱對性能形成影響。性能
public struct Employeestruct { private int _fooNumber; public Employeestruct(int fooNumber) : this() { _fooNumber = fooNumber; } public string Name { get; set; } public int Age { get; set; } public string GetMessage() { return string.Format("{0}--{1}", Name, Age); } }
從上面能夠看到,struct和class很是類似,不過它們仍是有本質的區別,接下來咱們就一一分析。this
Structs從System.ValueType繼承而classes繼承於System.Object或從System.Object繼承的其餘類型,固然System.ValueType也從System.Object繼承(這不是重點)。Structs能夠實現接口,但不能從另外一個classes、structs繼承,並且不能做爲其餘classes的基類。要知道,當你把structs做爲接口使用時,就進行一次隱形裝箱,由於接口做爲引用類型。請看下面代碼:spa
struct Foo : IFoo { int x; } IFoo iFoo = new Foo();
Foo的實例被建立並賦值給iFoo(裝箱),當iFoo調用時實際上使用的是Foo裝箱後的實例。.net
儘管CLR容許,可是不容許在structs中定義無參的構造函數。對於值類型,編譯器默認狀況下不生成默認的構造函數,也不調用默認的構造函數,因此C#編譯器不容許使用者定義無參的構造函數。因爲structs不生成默認的構造函數,因此不能初始化字段。以下(錯誤):code
Struct MyFoo { int x = 1; }
記住,編譯器把初始化工做放在構造函數中,因爲structs沒有默認的構造函數,因此不能初始化字段。orm
有趣的是,你可使用下面的語法:對象
Foo foo = new Foo();
經過以前的章節瞭解到,儘管初始化foo使用了new操做符,可是structs的實例分配到棧上。new Foo()不會調用無參的構造函數,僅僅是初始化該struct的字段爲默認值。
struct Foo { int x; public Foo(int x) { this.x = x; } } Foo foo = new Foo();
注意,我已經重載了構造函數,然而我可以調用new Foo()。
Structs不容許定義析構函數,若是你扔就去定義一個析構函數,編譯器會當即提示一個錯誤,不過structs能夠實現IDisposable接口。
Structs不能和null進行比較,這點在.net framework2.0已再也不是問題(可空類型),關於可空類型,超出本文討論範圍。
對於引用類型,readonly阻止再爲變量賦值,但不阻止你修改當前引用對象的狀態。對於值類型,readonly有點相似C++中的const關鍵字,它阻止你修改引用對象,也意味着不能再爲他賦值,由於這會致使從新初始化一次。請看下面代碼:
public class MyReferenceType { public int State { get; set; } } public struct MyValueType { public int State { get; set; } } public void TestMethod() { myReferenceType = new MyReferenceType(); // 錯誤 myReferenceType.State = 1234; // Ok myValueType = new MyValueType(); // 錯誤 myValueType.State = 1234; // 錯誤 }
foreach 語句和using語句的變量爲隱式readonly,因此若是使用structs,將沒法更改其狀態。
經過前面的描述已經清楚classes和structs的區別,那咱們來看下適合使用structs的場景:
l 實例使用起來像C#的基元類型
l 須要建立大量的、短暫實例(例如在循環體內)
l 實例不須要大量的傳遞
l 不須要從其餘類型繼承或不但願被其餘類型繼承
l 但願被人操做你實例的副本
不適合使用structs的場景:
l 實例過大,當實例過大時,傳遞實例會有很大的開銷。微軟建議structs的理想大小應該在16bytes如下
l 當回引發裝箱、拆箱操做時。關於裝箱、拆箱超出本文討論範圍