C#語(yǔ)言struct結(jié)構(gòu)體適用場(chǎng)景和注意事項(xiàng)
在C#語(yǔ)言中struct結(jié)構(gòu)體和class之間的區(qū)別主要是值類(lèi)型和引用類(lèi)型的區(qū)別,但實(shí)際上如果使用不當(dāng)是非常要命的。從Win32時(shí)代過(guò)來(lái)的人對(duì)于struct一點(diǎn)不感覺(jué)陌生,但是卻反而忽略了一些基本問(wèn)題。我們知道C#在涉及到本地代碼的地方大量使用了struct,很大程度上是為了移植代碼的需要。很多時(shí)候,感覺(jué)結(jié)構(gòu)比較簡(jiǎn)單的類(lèi)改為struct可能會(huì)提高性能,但這種感覺(jué)在絕大多數(shù)情況下其實(shí)是錯(cuò)誤的。那么我們自己在編寫(xiě)代碼的時(shí)候究竟在什么情況下適合定義struct而不是class呢?
選用struct的原則
通過(guò)閱讀微軟的技術(shù)文章Choosing Between Class and Struct,可以了解到選擇使用struct的一些準(zhǔn)則。
考慮 定義struct而非class,如果類(lèi)型的實(shí)例很小而且通常存活期都很短或者一般都嵌入到其它對(duì)象中使用
避免 定義struct除非類(lèi)型滿(mǎn)足以下全部特征:
- 邏輯上表達(dá)了一個(gè)單一值,類(lèi)似基本數(shù)據(jù)類(lèi)型(int, double)
- 實(shí)例大小低于16字節(jié)
- 不可改變
- 不會(huì)被頻繁裝箱
個(gè)人總結(jié)了一些使用場(chǎng)景和注意的地方。
- 對(duì)于初學(xué)者或者一般情況,請(qǐng)使用class不要考慮struct。當(dāng)程序需要考慮性能而進(jìn)行優(yōu)化的階段再考慮struct問(wèn)題
- 定義struct時(shí),盡量作為私有類(lèi)型或內(nèi)部類(lèi)型,不要公開(kāi)
- struct的屬性不要定義公開(kāi)的set方法,也就是不可改變
- 使用struct管理非托管資源時(shí),定義Free方法,使用時(shí)一定要在恰當(dāng)時(shí)機(jī)調(diào)用Free。千萬(wàn)不要想著去實(shí)現(xiàn)IDisposable接口。如果覺(jué)得不安全,那就改用class吧!
- 如果需要調(diào)用本地代碼而迫不得已,才可以無(wú)視其它原則而選用struct
struct的性能
選用struct可以在一些特定條件下改善程序性能,但請(qǐng)注意,沒(méi)有“銀彈”能夠在所有情況下解決所有問(wèn)題。
struct一般用于一些結(jié)構(gòu)簡(jiǎn)單,可以用單一值概念描述的類(lèi)型。同時(shí),類(lèi)型的存活期應(yīng)該不會(huì)太長(zhǎng)。struct無(wú)需創(chuàng)建即可使用,也沒(méi)有垃圾回收問(wèn)題。struct壓根就不在GC堆內(nèi)存中分配,而是直接在棧內(nèi)存中分配。在使用struct時(shí)都會(huì)復(fù)制到當(dāng)前棧內(nèi)存中,就像其它值類(lèi)型一樣。以上這些特性只能說(shuō)和class在使用上會(huì)有差異,需要注意。但說(shuō)不上是優(yōu)點(diǎn)還是缺點(diǎn),取決于用法和具體情況。另外,struct不存在并發(fā)競(jìng)爭(zhēng)問(wèn)題,多線(xiàn)程安全,這應(yīng)該算是優(yōu)點(diǎn)了。
一種已知情況可以用struct來(lái)優(yōu)化程序,就是struct類(lèi)型的數(shù)組(注意是數(shù)組不是List,至于基于哈希的集合不好說(shuō))。struct數(shù)組在物理上一定是一個(gè)連續(xù)的內(nèi)存塊。如果是引用類(lèi)型,則物理上一般是分配指針來(lái)指向引用的實(shí)例,此時(shí)數(shù)組的內(nèi)存塊不能涵蓋所有要訪(fǎng)問(wèn)的數(shù)據(jù)。而struct數(shù)組在這種情況下所有會(huì)用到的數(shù)據(jù)都在數(shù)組的物理內(nèi)存之中包含,可以直接訪(fǎng)問(wèn)到,無(wú)需通過(guò)GC堆內(nèi)存的對(duì)象引用來(lái)反復(fù)的間接查找。同時(shí),如果實(shí)例數(shù)量非常多時(shí),使用struct數(shù)組還能避免大量分散在GC堆中的對(duì)象實(shí)例,從而減輕GC壓力。這里理想化的認(rèn)為struct的定義中所有字段都是值類(lèi)型的,不包含string等引用類(lèi)型。
此時(shí),對(duì)struct數(shù)組中的下標(biāo)訪(fǎng)問(wèn)不會(huì)造成復(fù)制(List的下標(biāo)訪(fǎng)問(wèn)則會(huì)),直接內(nèi)存定位效率很高。
int id = structArray[i].Id;
注意,struct字段不可變會(huì)很有幫助,如果需要修改字段內(nèi)容,通過(guò)ref方法。
定義:
public static void SetId(ref structType target, int value) { target.Id = value; }
使用:
SetId(ref structArray[i], 100);
實(shí)際上很多情況下,struct反而會(huì)拖慢我們的程序。由于值類(lèi)型在使用上的復(fù)制特性,定義一個(gè)龐大的struct在絕大多數(shù)情況下性能會(huì)比引用類(lèi)型要糟糕。因?yàn)槊看问褂玫絪truct時(shí)都會(huì)在棧中復(fù)制一份新實(shí)例,復(fù)制來(lái)復(fù)制去的,如果struct的定義的字段比較多占用很多字節(jié)的話(huà),復(fù)制的成本就會(huì)很高。這也是為什么微軟給出的準(zhǔn)則中有一條:“當(dāng)類(lèi)型定義大于16字節(jié)時(shí)不要選用struct”。
struct是不可變的!
首先,從邏輯上,一個(gè)struct描述了一個(gè)單一值,struct的所有公開(kāi)的屬性、字段都應(yīng)該是用于獲取這個(gè)單一值的一些特征的,這從邏輯上就杜絕了可賦值的屬性這樣的定義。
其次,由于struct是值類(lèi)型,分配在棧內(nèi)存中或者是擁有struct類(lèi)型的引用類(lèi)型對(duì)象中,任何時(shí)候?qū)truct的訪(fǎng)問(wèn)都會(huì)訪(fǎng)問(wèn)原始struct的副本,因此對(duì)struct屬性的修改實(shí)際上是在修改原始struct的副本。除非你將修改后的struct實(shí)例重新賦值回去,否則原始struct是不會(huì)改變。這一特性同樣適用于函數(shù)方法的參數(shù)是struct的情況。
當(dāng)然,要直接改變?cè)約truct也是有辦法的,那就是使用ref類(lèi)型的的方法參數(shù)來(lái)直接改變?cè)贾怠5@就需要定義一個(gè)專(zhuān)門(mén)的方法,通過(guò)struct的屬性來(lái)訪(fǎng)問(wèn)時(shí)仍然會(huì)有上述問(wèn)題。
用struct管理本地代碼
用struct管理本地代碼時(shí),注意定義釋放方法,而使用時(shí)要在恰當(dāng)時(shí)機(jī)去明確調(diào)用釋放方法。
struct沒(méi)有明確的無(wú)參構(gòu)造方法,也沒(méi)有析構(gòu)方法。這是因?yàn)閟truct本身就是一份棧內(nèi)存,無(wú)需new新的實(shí)例,也無(wú)需去釋放。
但如果struct內(nèi)部使用了本地資源,這時(shí)本地資源的釋放就成了問(wèn)題。對(duì)于object的class類(lèi)型,我們可以定義實(shí)現(xiàn)IDisposable接口,在使用時(shí)用using代碼塊來(lái)創(chuàng)建實(shí)例。但是對(duì)于struct來(lái)說(shuō),千萬(wàn)不要。因?yàn)樵趗sing的時(shí)候使用的是struct的副本,而內(nèi)存中可能存在很多很多struct的副本。這種情況下,Dispose的邏輯應(yīng)當(dāng)非常可靠才能避免重復(fù)釋放的問(wèn)題。
實(shí)際上,用struct來(lái)管理本地資源的情況一定要將struct定義為私有或內(nèi)部,作為一個(gè)公開(kāi)類(lèi)型的內(nèi)部實(shí)現(xiàn)。這樣可以保證所有使用的實(shí)例都能夠被干凈釋放,避免內(nèi)存泄漏。

浙公網(wǎng)安備 33010602011771號(hào)