網站首頁 編程語言 正文
我們知道,值類型的變量是在堆棧上分配內存的,而引用類型包括System.Object的對象是在堆上分配內存的,基于這一特點,當值類型被類型轉換時,會在堆棧和堆上進行一系列的操作,這就是裝箱和拆箱的來源。充分理解裝箱和拆箱,有助于程序員編寫高效率的代碼。
1、裝箱和拆箱的基本概念
我們知道,所有的值類型都繼承自System.ValueType,而System.ValueType繼承自System.Object。所有的值類型對象都分配在堆棧上,而所有的引用類型包括System.Object對象都分配在堆上。問題隨之而來,既然System.Object是所有值類型的基類,那所有的值類型必然都可以隱式的轉換成System.Object類型,此時這個對象會被放在哪里呢,堆棧上面還是堆上面?實際上,當這個轉換發生時,CLR需要做額外的工作把堆棧上的值類型移動到堆上,這個操作就被稱為裝箱。來看一個裝箱所需要的詳細步驟。
- 在堆上分配一個內存空間,大小等于需要裝箱的值類型對象的大小加上兩個引用類型對象都擁有的成員:類型對象指針和同步塊引用。
- 把堆棧上的值類型對象復制到堆上新分配的對象。
- 返回一個指向堆上新對象的引用,并且存儲到堆棧上被裝箱的那個值類型的對象里。
這些步驟都不需要程序員自己編寫,在任何出現裝箱的地方,編譯器會自動地加上執行以上功能的中間代碼。下圖展示了裝箱前后堆和堆棧的變化。
理解了裝箱之后,就可以很方便地理解拆箱操作了。所謂的拆箱,就是裝箱操作的反操作,把堆中的對象復制到堆棧中,并且返回其值。需要注意的是,拆箱操作將判斷被拆箱的對象類型和將要被復制的值類型引用是否一致,如果不一致,將會拋出一個InvalidCastException的異常。這里的類型匹配并不采用任何顯示的類型轉換。下面的代碼展示了這一特性。
static void Main(string[] args) { try { Int32 i = 3; // 裝箱 Object o = i; // 拆箱,類型轉換失敗 Int16 j = (Int16)o; } catch (Exception ex) { Console.WriteLine(ex.Message); } Int32 ii = 3; // 裝箱 Object obj = ii; // 拆箱 Int16 jj = (Int16)(Int32)obj; Console.WriteLine("拆箱成功!"); Console.ReadKey(); }
程序運行結果:
分析上面的代碼,在第一組裝箱拆箱操作中,代碼試圖把一個原來類型為Int32的值類型裝箱后的對象拆箱成Int16的變量,這樣的拆箱是非法的,運行時會拋出一個InvalidCastException的異常。而在第二組裝箱拆箱操作中,就進行了正確的類型匹配,拆箱順利完成。
2、裝箱和拆箱對性能的影響,以及如何避免裝箱拆箱
裝箱和拆箱都意味著堆和堆棧空間的一系列操作,毫無疑問,這些操作的性能代價是很大的,尤其對于堆上空間的操作,速度相對于堆棧的操作慢的多,并且可能引發垃圾回收,這些都將大規模地影響系統的性能。如何避免裝箱拆箱操作,是程序員在編寫代碼時需要時刻考慮的一個問題。裝箱和拆箱操作常發生在以下兩個場合:
- 值類型的格式化輸出。
- System.Object類型的容器。
第一種情況,值類型的格式化輸出往往會涉及一次裝箱操作。例如下面的兩行代碼:
int i = 10; Console.WriteLine("i的值是:" + i);
代碼完全能夠通過編譯并且正確執行,但卻引發了一次不必要的裝箱操作。在第2行代碼上,值類型i被作為一個System.Object對象傳入方法之中,這樣的操作完全可以通過下面的改動來避免:
int i = 10; Console.WriteLine("i的值是:" + i.ToString());
改動后的代碼調用了i的ToString()方法來得到一個字符串對象。由于字符串是引用類型,所以改動后的代碼就不在涉及裝箱操作。
第二種情況更為常見一些。例如常用的容器類ArrayList,就是一個典型的System.Object容器。任何值類型被放入ArrayList的對象中,都會引發一次裝箱操作。而對應的,取出值類型對象就會引發一次拆箱操作。在.NET 1.1之前,這樣的操作很難避免,但在.NET 2.0推出了泛型的概念后,這些問題得到了有效的解決。泛型允許定義針對某個特定類型(包括值類型)的容器,并且有效的避免裝箱和拆箱。
三、總結
裝箱和拆箱本質上是值類型在轉換到System.Object時引發的堆棧和堆的一系列移動操作。裝箱時值類型從堆棧上被復制到堆上,而拆箱時從堆上復制到堆棧上。裝箱和拆箱對性能有比較大的影響,應該避免任何沒有必要的裝箱和拆箱操作。
在可以確定類型的情況下應該使用泛型技術而避免使用針對System.Object類型的容器,這樣可以有效避免大規模地使用裝箱和拆箱操作。
原文鏈接:https://www.cnblogs.com/dotnet261010/p/12326344.html
相關推薦
- 2022-02-17 如何通過一道題,全方位地考察自己,是否已經完美掌握了:this指向、作用域&作用域鏈、閉包、
- 2022-08-17 R語言學習VennDiagram包繪制韋恩圖示例_R語言
- 2022-08-13 Linux新特性之btrfs文件系統
- 2022-12-15 Oracle?listagg去重distinct的三種方式總結_oracle
- 2023-05-15 使用Bash讀取和處理CSV文件的方法_linux shell
- 2022-10-26 Python?Numpy教程之排序,搜索和計數詳解_python
- 2022-06-08 nacos項目啟動報錯:Connection refused: no further informa
- 2022-06-12 Python中property屬性的用處詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支