關(guān)于C#?dynamic裝箱問(wèn)題
前言
前幾天在技術(shù)群里看到有同學(xué)在討論關(guān)于dynamic是否會(huì)存在裝箱拆箱的問(wèn)題,我當(dāng)時(shí)第一想法是"會(huì)"。至于為啥會(huì)有很多人有這種疑問(wèn),主要是因?yàn)橛X(jué)得dynamic可能是因?yàn)橛悬c(diǎn)特殊,因?yàn)樗环Q(chēng)為動(dòng)態(tài)類(lèi)型,可能是因?yàn)檫@里的動(dòng)態(tài)對(duì)大家造成的誤解,認(rèn)為這里的動(dòng)態(tài)可以推斷出具體的類(lèi)型,所以可以避免裝箱拆箱。但是事實(shí)并不是這樣,今天就一起就這個(gè)問(wèn)題雖然討論一下。
裝箱拆箱
首先咱們先來(lái)看下何為裝箱拆箱,這個(gè)可以在微軟官方文檔中Boxing and Unboxing文檔中看到答案,咱們就簡(jiǎn)單的摘要一下相關(guān)的描述
裝箱是將值類(lèi)型轉(zhuǎn)換為類(lèi)型對(duì)象或此值類(lèi)型實(shí)現(xiàn)的任何接口類(lèi)型的過(guò)程。當(dāng)公共語(yǔ)言運(yùn)行時(shí) (CLR) 將值類(lèi)型裝箱時(shí),它會(huì)將值包裝在 System.Object 實(shí)例中并將其存儲(chǔ)在托管堆上。拆箱從對(duì)象中提取值類(lèi)型。拳擊是隱含的;拆箱是明確的。裝箱和拆箱的概念是 C# 類(lèi)型系統(tǒng)統(tǒng)一視圖的基礎(chǔ),其中任何類(lèi)型的值都可以視為對(duì)象。
翻譯起來(lái)會(huì)比較抽象,理解起來(lái)就是利用裝箱和拆箱功能,可通過(guò)允許值類(lèi)型的任何值與Object 類(lèi)型的值相互轉(zhuǎn)換,將值類(lèi)型與引用類(lèi)型鏈接起來(lái)。也就是值類(lèi)型和引用類(lèi)型相互轉(zhuǎn)換的一做橋梁,但是問(wèn)題也很明顯那就是實(shí)例會(huì)存在在堆棧之前相互copy的問(wèn)題,會(huì)存在一定的性能問(wèn)題,所以這也一直是一個(gè)詬病。
雖然說(shuō)是這樣但是也沒(méi)必要一直扣死角,畢竟很多時(shí)候程序還沒(méi)有糾結(jié)到這種程度,因?yàn)槿魏握Z(yǔ)言存在的各種方法中或者操作中都會(huì)有一定這種問(wèn)題,所以本質(zhì)不是語(yǔ)言存在各種問(wèn)題,而是在什么場(chǎng)景如何使用的問(wèn)題。比如避免出現(xiàn)裝箱和拆箱的辦法也就是入概念所說(shuō)的,那就是避免值類(lèi)型和和引用類(lèi)型之間相互轉(zhuǎn)換,但是很多時(shí)候還是避免不了的,所以也不必糾結(jié)。
探究本質(zhì)
上面講解了關(guān)于裝箱拆箱的概念,接下來(lái)咱們就來(lái)定義一段代碼看看效果,為了方便對(duì)比咱們直接對(duì)比著看一下
dynamic num = 123; dynamic str = "a string";
想要看清本質(zhì)還是要反編譯一下生成的結(jié)果看一下的,這里我們可以借助ILSpy或dnSpy來(lái)看下,首先看一下反編譯回來(lái)的效果
private static void <Main>$(string[] args)
{
object num = 123;
object str = "a string";
Console.ReadKey();
}因?yàn)槲沂鞘褂玫氖?net6的頂級(jí)聲明方式所以會(huì)生成<Main>$方法。不過(guò)從反編譯的結(jié)果就可以看出來(lái)dynamic的本質(zhì)是object,如果還有點(diǎn)懷疑的話(huà)可以直接查看生成的IL代碼,還是使用ILSpy工具
.method private hidebysig static
void '<Main>$' (
string[] args
) cil managed
{
// Method begins at RVA 0x2094
// Header size: 12
// Code size: 30 (0x1e)
.maxstack 1
.entrypoint
.locals init (
// 這里可以看出聲明的num和str變量都是object類(lèi)型的
[0] object num,
[1] object str
)
// object obj = 123;
IL_0000: ldc.i4.s 123
// 這里的box說(shuō)明存在裝箱操作
IL_0002: box [System.Runtime]System.Int32
IL_0007: stloc.0
// object obj2 = "a string";
IL_0008: ldstr "a string"
IL_000d: stloc.1
// Console.ReadKey();
IL_000e: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey()
IL_0013: pop
// (no C# code)
IL_0014: nop
IL_0015: nop
IL_0016: nop
IL_0017: nop
IL_0018: nop
IL_0019: nop
IL_001a: nop
IL_001b: nop
// }
IL_001c: nop
IL_001d: ret
} // end of method Program::'<Main>$'通過(guò)這里可以看出dynamic的本質(zhì)確實(shí)是object,既然是object那就可以證實(shí)確實(shí)是存在裝箱操作。這個(gè)其實(shí)在微軟官方文檔Using type dynamic上有說(shuō)明,大致描述是這樣的
dynamic類(lèi)型是一種靜態(tài)類(lèi)型,但類(lèi)型為dynamic的對(duì)象會(huì)跳過(guò)靜態(tài)類(lèi)型檢查。大多數(shù)情況下,該對(duì)象就像具有類(lèi)型object一樣。 在編譯時(shí),將假定類(lèi)型化為dynamic的元素支持任何操作。因此,不必考慮對(duì)象是從 COM API、從動(dòng)態(tài)語(yǔ)言(例如 IronPython)、從 HTML 文檔對(duì)象模型 (DOM)、從反射還是從程序中的其他位置獲取自己的值。但是,如果代碼無(wú)效,則在運(yùn)行時(shí)會(huì)捕獲到錯(cuò)誤。
從這里可以看出dynamic表現(xiàn)出來(lái)的就是object,只是dynamic會(huì)跳過(guò)靜態(tài)類(lèi)型檢查,所以編譯的時(shí)候不會(huì)報(bào)錯(cuò),有錯(cuò)誤的話(huà)會(huì)在運(yùn)行的時(shí)候報(bào)錯(cuò),也就是我們說(shuō)的是在運(yùn)行時(shí)確定具體操作。這涉及到動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí),動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)(DLR)是一種運(yùn)行時(shí)環(huán)境,可以將一組動(dòng)態(tài)語(yǔ)言服務(wù)添加到公共語(yǔ)言運(yùn)行時(shí)(CLR)。使用DLR可以輕松開(kāi)發(fā)在.NET上運(yùn)行的動(dòng)態(tài)語(yǔ)言,并為靜態(tài)類(lèi)型語(yǔ)言添加動(dòng)態(tài)特征。
匿名類(lèi)型
總會(huì)有人拿dynamic和var進(jìn)行比較,但是本質(zhì)上來(lái)說(shuō),這兩者描述的不是一個(gè)層面的東西。var叫隱式類(lèi)型,本質(zhì)是一種語(yǔ)法糖,也就是說(shuō)在編譯的時(shí)候就可以確定類(lèi)型的具體類(lèi)型,也就是說(shuō)var本質(zhì)是提供了一種更簡(jiǎn)單的編程體驗(yàn),不會(huì)影響變量本身的行為。這也就解釋了為啥同一個(gè)var變量多次賦值不能賦不同類(lèi)型的值,比如以下操作編譯器會(huì)直接報(bào)錯(cuò)
var num = 123; num = "123"; //報(bào)錯(cuò)
如果你是用的集成開(kāi)發(fā)環(huán)境的話(huà)其實(shí)很容易發(fā)現(xiàn),把鼠標(biāo)放到var類(lèi)型上就會(huì)顯示變量對(duì)應(yīng)的真實(shí)類(lèi)型?;蛘呖梢灾苯油ㄟ^(guò)ILSpy看看反編譯結(jié)果,比如聲明了var num = 123編譯完成之后就是
private static void <Main>$(string[] args)
{
int num = 123;
Console.ReadKey();
}請(qǐng)注意這里并不是object而是轉(zhuǎn)換成了具體的類(lèi)型因?yàn)?code>123就是int類(lèi)型的,嚴(yán)謹(jǐn)一點(diǎn)看一下IL代碼
.maxstack 1 .entrypoint //聲明的int32 .locals init ( [0] int32 num ) // int num = 123; IL_0000: ldc.i4.s 123 IL_0002: stloc.0
相信這里就可以看出來(lái)了dynamic和var確實(shí)也不是一個(gè)層面的東西。var是隱式類(lèi)型是語(yǔ)法糖為了簡(jiǎn)化編程體驗(yàn)用的,dynamic則是動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)技術(shù),編譯時(shí)轉(zhuǎn)換成object類(lèi)型,因?yàn)樵赾#上一切都是object,然后再運(yùn)行時(shí)進(jìn)行具體的操作。
總結(jié)
本篇文章主要是在技術(shù)群里看到有同學(xué)在討論關(guān)于dynamic是否會(huì)裝箱引發(fā)的思考,相對(duì)來(lái)說(shuō)講解的比較基礎(chǔ)也比較簡(jiǎn)單。想對(duì)一個(gè)東西理解的更透徹,就要一步一步的了解它到底是什么,這樣的話(huà)就可以更好的理解和思考。也印證了那句話(huà),你不會(huì)用或者用是因?yàn)槟銓?duì)它不夠了解,當(dāng)你對(duì)它有足夠理解的時(shí)候,操作起來(lái)也就會(huì)游刃有余。
到此這篇關(guān)于關(guān)于C# dynamic裝箱引發(fā)的思考的文章就介紹到這了,更多相關(guān)C# dynamic裝箱內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Unity實(shí)現(xiàn)卡拉OK歌詞過(guò)渡效果
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)卡拉OK歌詞過(guò)渡效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
C#的FileSystemWatcher用法實(shí)例詳解
這篇文章主要介紹了C#的FileSystemWatcher用法,以實(shí)例形似詳細(xì)分析了FileSystemWatcher控件主要功能,并總結(jié)了FileSystemWatcher控件使用的技巧,需要的朋友可以參考下2014-11-11
c# 獲取CookieContainer的所有cookies函數(shù)代碼
這篇文章主要介紹了c# 獲取CookieContainer所有cookies的函數(shù)代碼,需要的朋友可以參考下2013-06-06
C#實(shí)現(xiàn)發(fā)送手機(jī)驗(yàn)證碼功能
之前基于c#實(shí)現(xiàn)手機(jī)發(fā)送驗(yàn)證碼功能很復(fù)雜,真正做起來(lái)也就那回事,不過(guò)就是一個(gè)post請(qǐng)求就可以實(shí)現(xiàn)的東西,今天小編把思路分享到腳本之家平臺(tái),供大家參考下2017-06-06
C#實(shí)現(xiàn)Check Password和鎖定輸錯(cuò)密碼鎖定賬戶(hù)功能
C#實(shí)現(xiàn)的Check Password,并根據(jù)輸錯(cuò)密碼的次數(shù)分情況鎖定賬戶(hù):如果輸入錯(cuò)誤3次,登錄賬戶(hù)鎖定5分鐘并提示X點(diǎn)X分后重試登錄,具體實(shí)現(xiàn)代碼感興趣的朋友跟隨小編一起看看吧2020-01-01

