C#語言struct結構體適用場景和注意事項

在C#語言中struct結構體和class之間的區別主要是值類型和引用類型的區別,但實際上若是使用不當是很是要命的。從Win32時代過來的人對於struct一點不感受陌生,可是卻反而忽略了一些基本問題。咱們知道C#在涉及到本地代碼的地方大量使用了struct,很大程度上是爲了移植代碼的須要。不少時候,感受結構比較簡單的類改成struct可能會提升性能,但這種感受在絕大多數狀況下實際上是錯誤的。那麼咱們本身在編寫代碼的時候究竟在什麼狀況下適合定義struct而不是class呢?數組

選用struct的原則

經過閱讀微軟的技術文章Choosing Between Class and Struct,能夠了解到選擇使用struct的一些準則。安全

考慮 定義struct而非class,若是類型的實例很小並且一般存活期都很短或者通常都嵌入到其它對象中使用多線程

避免 定義struct除非類型知足如下所有特徵:併發

  • 邏輯上表達了一個單一值,相似基本數據類型(int, double)
  • 實例大小低於16字節
  • 不可改變
  • 不會被頻繁裝箱

我的總結了一些使用場景和注意的地方。函數

  • 對於初學者或者通常狀況,請使用class不要考慮struct。當程序須要考慮性能而進行優化的階段再考慮struct問題
  • 定義struct時,儘可能做爲私有類型或內部類型,不要公開
  • struct的屬性不要定義公開的set方法,也就是不可改變
  • 使用struct管理非託管資源時,定義Free方法,使用時必定要在恰當時機調用Free。千萬不要想着去實現IDisposable接口。若是以爲不安全,那就改用class吧!
  • 若是須要調用本地代碼而無可奈何,才能夠無視其它原則而選用struct

struct的性能

選用struct能夠在一些特定條件下改善程序性能,但請注意,沒有「銀彈」可以在全部狀況下解決全部問題。性能

struct通常用於一些結構簡單,能夠用單一值概念描述的類型。同時,類型的存活期應該不會太長。struct無需建立便可使用,也沒有垃圾回收問題。struct壓根就不在GC堆內存中分配,而是直接在棧內存中分配。在使用struct時都會複製到當前棧內存中,就像其它值類型同樣。以上這些特性只能說和class在使用上會有差別,須要注意。但說不上是優勢仍是缺點,取決於用法和具體狀況。另外,struct不存在併發競爭問題,多線程安全,這應該算是優勢了。優化

一種已知狀況能夠用struct來優化程序,就是struct類型的數組(注意是數組不是List,至於基於哈希的集合很差說)。struct數組在物理上必定是一個連續的內存塊。若是是引用類型,則物理上通常是分配指針來指向引用的實例,此時數組的內存塊不能涵蓋全部要訪問的數據。而struct數組在這種狀況下全部會用到的數據都在數組的物理內存之中包含,能夠直接訪問到,無需經過GC堆內存的對象引用來反覆的間接查找。同時,若是實例數量很是多時,使用struct數組還能避免大量分散在GC堆中的對象實例,從而減輕GC壓力。這裏理想化的認爲struct的定義中全部字段都是值類型的,不包含string等引用類型。線程

此時,對struct數組中的下標訪問不會形成複製(List的下標訪問則會),直接內存定位效率很高。指針

int id = structArray[i].Id;

注意,struct字段不可變會頗有幫助,若是須要修改字段內容,經過ref方法。 定義:code

public static void SetId(ref structType target, int value) 
{ 
	target.Id = value; 
}

使用:

SetId(ref structArray[i], 100);

實際上不少狀況下,struct反而會拖慢咱們的程序。因爲值類型在使用上的複製特性,定義一個龐大的struct在絕大多數狀況下性能會比引用類型要糟糕。由於每次使用到struct時都會在棧中複製一份新實例,複製來複制去的,若是struct的定義的字段比較多佔用不少字節的話,複製的成本就會很高。這也是爲何微軟給出的準則中有一條:「當類型定義大於16字節時不要選用struct」。

struct是不可變的!

首先,從邏輯上,一個struct描述了一個單一值,struct的全部公開的屬性、字段都應該是用於獲取這個單一值的一些特徵的,這從邏輯上就杜絕了可賦值的屬性這樣的定義。

其次,因爲struct是值類型,分配在棧內存中或者是擁有struct類型的引用類型對象中,任什麼時候候對struct的訪問都會訪問原始struct的副本,所以對struct屬性的修改其實是在修改原始struct的副本。除非你將修改後的struct實例從新賦值回去,不然原始struct是不會改變。這一特性一樣適用於函數方法的參數是struct的狀況。

固然,要直接改變原始struct也是有辦法的,那就是使用ref類型的的方法參數來直接改變原始值。但這就須要定義一個專門的方法,經過struct的屬性來訪問時仍然會有上述問題。

用struct管理本地代碼

用struct管理本地代碼時,注意定義釋放方法,而使用時要在恰當時機去明確調用釋放方法。

struct沒有明確的無參構造方法,也沒有析構方法。這是由於struct自己就是一份棧內存,無需new新的實例,也無需去釋放。

但若是struct內部使用了本地資源,這時本地資源的釋放就成了問題。對於object的class類型,咱們能夠定義實現IDisposable接口,在使用時用using代碼塊來建立實例。可是對於struct來講,千萬不要。由於在using的時候使用的是struct的副本,而內存中可能存在不少不少struct的副本。這種狀況下,Dispose的邏輯應當很是可靠才能避免重複釋放的問題。

實際上,用struct來管理本地資源的狀況必定要將struct定義爲私有或內部,做爲一個公開類型的內部實現。這樣能夠保證全部使用的實例都可以被幹淨釋放,避免內存泄漏。

相關文章
相關標籤/搜索