C# 之裝箱與拆箱
知識點
- 值型別。
- 值型別是在棧中分配記憶體,在宣告時初始化才能使用,不能為null。
- 值型別超出作用範圍系統自動釋放記憶體。
- 主要由兩類組成:結構,列舉(enum),結構分為以下幾類:
- 整型(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong)
- 浮點型(Float、Double)
- decimal
- bool
- 使用者定義的結構(struct)
- 引用型別。
- 引用型別在堆中分配記憶體,初始化時預設為null。
- 引用型別是通過垃圾回收機制進行回收。
- 包括類、介面、委託、陣列以及內建引用型別object與string。
概念
由於C#中所有的資料型別都是由基類System.Object繼承而來的,所以值型別和引用型別的值可以通過顯式(或隱式)操作相互轉換,而這轉換過程也就是裝箱(boxing)和拆箱(unboxing)過程。
- 裝箱 是值型別到 object 型別或到此值型別所實現的任何介面型別的隱式轉換(就算實現了某個介面,轉為介面依舊要裝箱)。對值型別裝箱會在堆中分配一個物件例項,並將該值複製到新的物件中。
- 拆箱(取消裝箱)是從 object 型別到值型別或從介面型別到實現該介面的值型別的顯式轉換。取消裝箱操作包括:
-
檢查物件例項,確保它是給定值型別的一個裝箱值。(拆箱後沒有轉成原型別,編譯時不會出錯,但執行會出錯,所以一定要確保這一點。用GetType().ToString()判斷時一定要使用型別全稱,如:System.String 而不要用String。)
-
將該值從例項複製到值型別變數中。
示例
首先寫個簡單的控制檯程式:
C#程式碼- // Tutorial_boxing_unboxing.cs
- // 裝箱與拆箱
- using System;
- class App
- {
- staticvoid Main()
- {
- int i = 32;
- object o = i; //隱式裝箱
- Console.WriteLine(”o = {0}”, o);
- Console.Read();
- }
- }
// Tutorial_boxing_unboxing.cs // 裝箱與拆箱 using System; class App { static void Main() { int i = 32; object o = i; //隱式裝箱 Console.WriteLine("o = {0}", o); Console.Read(); } }
其中object o = i這裡我們進行了裝箱操作,然後我們用MSIL 反彙編程式檢視下生成的.exe程式的內部機理。
C程式碼- 1 .method private hidebysig staticvoid Main() cil managed
- 2 {
- 3 .entrypoint
- 4 // 程式碼大小 30 (0x1e)
- 5 .maxstack 2
- 6 .locals init ([0] int32 i,
- 7 [1] object o)
- 8 IL_0000: nop
- 9 IL_0001: ldc.i4.s 32
- 10 IL_0003: stloc.0
- 11 IL_0004: ldloc.0
- 12 IL_0005: box [mscorlib]System.Int32
- 13 IL_000a: stloc.1
- 14 IL_000b: ldstr ”o = {0}”
- 15 IL_0010: ldloc.1
- 16 IL_0011: call void [mscorlib]System.Console::WriteLine(string,
- 17 object)
- 18 IL_0016: nop
- 19 IL_0017: call int32 [mscorlib]System.Console::Read()
- 20 IL_001c: pop
- 21 IL_001d: ret
- 22 } // end of method App::Main
1 .method private hidebysig static void Main() cil managed 2 { 3 .entrypoint 4 // 程式碼大小 30 (0x1e) 5 .maxstack 2 6 .locals init ([0] int32 i, 7 [1] object o) 8 IL_0000: nop 9 IL_0001: ldc.i4.s 32 10 IL_0003: stloc.0 11 IL_0004: ldloc.0 12 IL_0005: box [mscorlib]System.Int32 13 IL_000a: stloc.1 14 IL_000b: ldstr "o = {0}" 15 IL_0010: ldloc.1 16 IL_0011: call void [mscorlib]System.Console::WriteLine(string, 17 object) 18 IL_0016: nop 19 IL_0017: call int32 [mscorlib]System.Console::Read() 20 IL_001c: pop 21 IL_001d: ret 22 } // end of method App::Main
其中第12行是我們的裝箱操作。(關於IL中出現的操作符代表的操作請查閱MSDN Library中的.NET開發/.NET Framework SDK/類庫參考/System.Reflection.Emit/OpCodes 類/OpCodes 欄位)
然後我們取消裝箱操作:
C#程式碼- staticvoid Main()
- {
- int i = 32;
- Console.WriteLine(”i = {0}”, i);
- Console.Read();
- }
static void Main() { int i = 32; Console.WriteLine("i = {0}", i); Console.Read(); }
再用MSIL工具檢視生成的.exe,如下結果:
C程式碼- .method private hidebysig staticvoid Main() cil managed
- {
- .entrypoint
- // 程式碼大小 28 (0x1c)
- .maxstack 2
- .locals init ([0] int32 i)
- IL_0000: nop
- IL_0001: ldc.i4.s 32
- IL_0003: stloc.0
- IL_0004: ldstr ”i = {0}”
- IL_0009: ldloc.0
- IL_000a: box [mscorlib]System.Int32
- IL_000f: call void [mscorlib]System.Console::WriteLine(string,
- object)
- IL_0014: nop
- IL_0015: call int32 [mscorlib]System.Console::Read()
- IL_001a: pop
- IL_001b: ret
- } // end of method App::Main
.method private hidebysig static void Main() cil managed { .entrypoint // 程式碼大小 28 (0x1c) .maxstack 2 .locals init ([0] int32 i) IL_0000: nop IL_0001: ldc.i4.s 32 IL_0003: stloc.0 IL_0004: ldstr "i = {0}" IL_0009: ldloc.0 IL_000a: box [mscorlib]System.Int32 IL_000f: call void [mscorlib]System.Console::WriteLine(string, object) IL_0014: nop IL_0015: call int32 [mscorlib]System.Console::Read() IL_001a: pop IL_001b: ret } // end of method App::Main
在IL_000a行,我們發現這裡卻也出現了一個box!不過這步是在call System.Console::WriteLine(string, object)時發生的。我們對比前面我們手動boxing的IL程式碼,發現在我們手動boxing後就沒有這步box了。為什麼呢?
當我們在呼叫一些方法的過載版本時,由於編譯器找不到符合給定引數型別的過載方法,此時編譯器便去尋找到的最接近的版本,然後使用找到的方法,而其引數卻是我們傳入的值型別的基類如System.Object或者其實現的介面型別,接著編譯器為了求得與這個方法的原型一致,就必須對該值型別進行裝箱操作(轉換成引用型別)。
照這個說法當我們不手動boxing時,在呼叫了Console.WriteLine()方法輸出一個Int32型別值時,系統就要自動進行boxing。也就是說如果我們要對該輸出操作作5000次的迴圈,系統就要做5000次的boxing。這樣對效能便會有一定的影響,而且要使迴圈次數是100,000,000次呢,或者跟多!
此時我們便要想如何消除這不應該的效能損失!正如第一個程式是展示的,我們可以在需要的地方先進行boxing,這個原理很簡單,我們可以聯想到類似的做法:
C#程式碼- //當我們如下時:
- for (int i = 0; i < arr.Length; i++)
- {
- //
- }
- //我們更因該這樣:
- int L = arr.Length;
- for (int i = 0; i < L; i++)
- {
- //
- }
//當我們如下時: for (int i = 0; i < arr.Length; i++) { // } //我們更因該這樣: int L = arr.Length; for (int i = 0; i < L; i++) { // }
這樣,我們只要一次boxing,就可以避免讓系統重複的做這個操作。
用途
像在呼叫Console.WriteLine()的過程中系統自動進行boxing一樣,當我們在呼叫其它的一些方法的過載版本進行操所時,為了避免由於無謂的隱式裝箱所造成的效能損失,在執行這些多型別過載方法之前,最好先對值進行裝箱。一般是在處理大量資料需要對型別進行裝箱操作。
——————-一下是另一篇文章———————————–
1、
裝箱和拆箱是一個抽象的概念
2、
裝箱是將值型別轉換為引用型別 ;拆箱是將引用型別轉換為值型別
利用裝箱和拆箱功能,可通過允許值型別的任何值與Object 型別的值相互轉換,將值型別與引用型別連結起來
例如:
int val = 100;
object obj = val;
Console.WriteLine (“物件的值 = {0}”, obj);
這是一個裝箱的過程,是將值型別轉換為引用型別的過程
int val = 100;
object obj = val;
int num = (int) obj;
Console.WriteLine (“num: {0}”, num);
這是一個拆箱的過程,是將值型別轉換為引用型別,再由引用型別轉換為值型別的過程
注:被裝過箱的物件才能被拆箱
3、
.NET中,資料型別劃分為值型別和引用(不等同於C++的指標)型別,與此對應,記憶體分配被分成了兩種方式,一為棧,二為堆,注意:是託管堆。
值型別只會在棧中分配。
引用型別分配記憶體與託管堆。
託管堆對應於垃圾回收。
4:裝箱/拆箱是什麼?
裝箱:用於在垃圾回收堆中儲存值型別。裝箱是值型別到 object 型別或到此值型別所實現的任何介面型別的隱式轉換。
拆箱:從 object 型別到值型別或從介面型別到實現該介面的值型別的顯式轉換。
5:為何需要裝箱?(為何要將值型別轉為引用型別?)
一種最普通的場景是,呼叫一個含型別為Object的引數的方法,該Object可支援任意為型,以便通用。當你需要將一個值型別(如Int32)傳入時,需要裝箱。
另一種用法是,一個非泛型的容器,同樣是為了保證通用,而將元素型別定義為Object。於是,要將值型別資料加入容器時,需要裝箱。
6:裝箱/拆箱的內部操作。
裝箱:
對值型別在堆中分配一個物件例項,並將該值複製到新的物件中。按三步進行。
第一步:新分配託管堆記憶體(大小為值型別例項大小加上一個方法表指標和一個SyncBlockIndex)。
第二步:將值型別的例項欄位拷貝到新分配的記憶體中。
第三步:返回託管堆中新分配物件的地址。這個地址就是一個指向物件的引用了。
有人這樣理解:如果將Int32裝箱,返回的地址,指向的就是一個Int32。我認為也不是不能這樣理解,但這確實又有問題,一來它不全面,二來指向Int32並沒說出它的實質(在託管堆中)。
拆箱:
檢查物件例項,確保它是給定值型別的一個裝箱值。將該值從例項複製到值型別變數中。
有書上講,拆箱只是獲取引用物件中指向值型別部分的指標,而內容拷貝則是賦值語句之觸發。我覺得這並不要緊。最關鍵的是檢查物件例項的本質,拆箱和裝箱的型別必需匹配,這一點上,在IL層上,看不出原理何在,我的猜測,或許是呼叫了類似GetType之類的方法來取出型別進行匹配(因為需要嚴格匹配)。
7:裝箱/拆箱對執行效率的影響
顯然,從原理上可以看出,裝箱時,生成的是全新的引用物件,這會有時間損耗,也就是造成效率降低。
那該如何做呢?
首先,應該儘量避免裝箱。
比如上例2的兩種情況,都可以避免,在第一種情況下,可以通過過載函式來避免。第二種情況,則可以通過泛型來避免。
當然,凡事並不能絕對,假設你想改造的程式碼為第三方程式集,你無法更改,那你只能是裝箱了。
對於裝箱/拆箱程式碼的優化,由於C#中對裝箱和拆箱都是隱式的,所以,根本的方法是對程式碼進行分析,而分析最直接的方式是瞭解原理結何檢視反編譯的IL程式碼。比如:在迴圈體中可能存在多餘的裝箱,你可以簡單採用提前裝箱方式進行優化。
8:對裝箱/拆箱更進一步的瞭解
裝箱/拆箱並不如上面所講那麼簡單明瞭,比如:裝箱時,變為引用物件,會多出一個方法表指標,這會有何用處呢?
我們可以通過示例來進一步探討。
舉個例子。
- publicabstractclass ValueType
- {
- // Summary:
- // Initializes a new instance of the System.ValueType class.
- protected ValueType();
- publicoverridebool Equals(object obj);
- publicoverrideint GetHashCode();
- publicoverridestring ToString();
- }
- struct A : ICloneable
- {
- public Int32 x;
- publicobject Alf(int d)
- {
- returnthis.MemberwiseClone();
- }
- //public override String ToString()
- //{
- // return String.Format(“{0}”, x);
- //}
- publicobject Clone()
- {
- return MemberwiseClone();
- }
- }
- class Program
- {
- staticvoid Main(string[] args)
- {
- object o = 8;
- A a;
- a.x = 100;
- Console.WriteLine(a.ToString());//不裝箱
- Console.WriteLine(a.Equals(o));//不裝箱
- Console.WriteLine(a.GetHashCode());//不裝箱
- Console.WriteLine(a.GetType());//裝箱,因為System.ValueType沒有重寫這個方法
- A a2 = (A)a.Clone();
- ICloneable c = a2;
- object os = c.Clone();
- }
- }
public abstract class ValueType { // Summary: // Initializes a new instance of the System.ValueType class. protected ValueType(); public override bool Equals(object obj); public override int GetHashCode(); public override string ToString(); } struct A : ICloneable { public Int32 x; public object Alf(int d) { return this.MemberwiseClone(); } //public override String ToString() //{ // return String.Format("{0}", x); //} public object Clone() { return MemberwiseClone(); } } class Program { static void Main(string[] args) { object o = 8; A a; a.x = 100; Console.WriteLine(a.ToString());//不裝箱 Console.WriteLine(a.Equals(o));//不裝箱 Console.WriteLine(a.GetHashCode());//不裝箱 Console.WriteLine(a.GetType());//裝箱,因為System.ValueType沒有重寫這個方法 A a2 = (A)a.Clone(); ICloneable c = a2; object os = c.Clone(); } }
5.0:a.ToString()。編譯器發現A重寫了ToString方法,會直接呼叫ToString的指令。因為A是值型別,編譯器不會出現多型行為。因此,直接呼叫,不裝箱。(注:ToString是A的基類System.ValueType的方法)
5.1:a.GetType(),GetType是繼承於System.Object的方法,要呼叫它,需要一個方法表指標,於是a將被裝箱,從而生成方法表指標,呼叫基類的System.Object。(補一句,所有的值型別都是繼承於System.Object的)。
5.2:a.Clone(),因為A實現了Clone方法,所以無需裝箱。
5.3:ICloneable轉型:當a2為轉為介面型別時,必須裝箱,因為介面是一種引用型別。
5.4:c.Clone()。無需裝箱,在託管堆中對上一步已裝箱的物件進行呼叫。
附:其實上面的基於一個根本的原理,因為未裝箱的值型別沒有方法表指標,所以,不能通過值型別來呼叫其上繼承的虛方法。另外,介面型別是一個引用型別。對此,我的理解,該方法表指標類似C++的虛擬函式表指標,它是用來實現引用物件的多型機制的重要依據。
9:如何更改已裝箱的物件
對於已裝箱的物件,因為無法直接呼叫其指定方法,所以必須先拆箱,再呼叫方法,但再次拆箱,會生成新的棧例項,而無法修改裝箱物件。有點暈吧,感覺在說繞口令。還是舉個例子來說:(在上例中追加change方法)
public void Change(Int32 x) {
this.x = x;
}
呼叫:
A a = new A();
a.x = 100;
Object o = a; //裝箱成o,下面,想改變o的值。
((A)o).Change(200); //改掉了嗎?沒改掉。
沒改掉的原因是o在拆箱時,生成的是臨時的棧例項A,所以,改動是基於臨時A的,並未改到裝箱物件。
(附:在託管C++中,允許直接取加拆箱時第一步得到的例項引用,而直接更改,但C#不行。)
那該如何是好?
嗯,通過介面方式,可以達到相同的效果。
實現如下:
interface IChange {
void Change(Int32 x);
}
struct A : IChange {
…
}
呼叫:
((IChange)o).Change(200);//改掉了嗎?改掉了。
為啥現在可以改?
在將o轉型為IChange時,這裡不會進行再次裝箱,當然更不會拆箱,因為o已經是引用型別,再因為它是IChange型別,所以可以直接呼叫Change,於是,更改的也就是已裝箱物件中的欄位了,達到期望的效果。
10、————————–
將值型別轉換為引用型別,需要進行裝箱操作(boxing):
1、首先從託管堆中為新生成的引用物件分配記憶體。
2、然後將值型別的資料拷貝到剛剛分配的記憶體中。
3、返回託管堆中新分配物件的地址。
可以看出,進行一次裝箱要進行分配記憶體和拷貝資料這兩項比較影響效能的操作。
將引用內型轉換為值內型,需要進行拆箱操作(unboxing):
1、首先獲取託管堆中屬於值型別那部分欄位的地址,這一步是嚴格意義上的拆箱。
2、將引用物件中的值拷貝到位於執行緒堆疊上的值型別例項中。
經過這2步,可以認為是同boxing是互反操作。嚴格意義上的拆箱,並不影響效能,但伴隨這之後的拷貝資料的操作就會同boxing操作中一樣影響效能。
11、————————-
NET的所有型別都是由基類System.Object繼承過來的,包括最常用的基礎型別:int, byte, short,bool等等,就是說所有的事物都是物件。如果申明這些型別得時候都在堆(HEAP)中分配記憶體,會造成極低的效率!(箇中原因以及關於堆和棧得區別會在另一篇裡單獨得說說!)
.NET如何解決這個問題得了?正是通過將型別分成值型(value)和引用型(regerencetype),C#中定義的值型別包括原型別(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、列舉(enum)、結構(struct),引用型別包括:類、陣列、介面、委託、字串等。
值型就是在棧中分配記憶體,在申明的同時就初始化,以確保資料不為NULL;
引用型是在堆中分配記憶體,初始化為null,引用型是需要GARBAGE COLLECTION來回收記憶體的,值型不用,超出了作用範圍,系統就會自動釋放!
下面就來說裝箱和拆箱的定義!
裝箱就是隱式的將一個值型轉換為引用型物件。比如:
int i=0;
Syste.Object obj=i;
這個過程就是裝箱!就是將i裝箱!
拆箱就是將一個引用型物件轉換成任意值型!比如:
int i=0;
System.Object obj=i;
int j=(int)obj;
這個過程前2句是將i裝箱,後一句是將obj拆箱!
相關推薦
C# 之裝箱與拆箱
知識點 值型別。 值型別是在棧中分配記憶體,在宣告時初始化才能使用,不能為null。 值型別超出作用範圍系統自動釋放記憶體。 主要由兩類組成:結構,列舉(enum),結構分為以下幾類: 整型(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong) 浮點型(F
Java SE之裝箱與拆箱
fin valueof targe 單純 數值 test 構造 簡化 ble 對象包裝器、自動裝箱與拆箱 2016/11/30 晚 特點: 1.所有的基本類型都有一個包裝器類與之對應。[Integer,Boolean,Long,Character,Sh
【C#】裝箱與拆箱
在生活中,我們都喜歡吃水果。大家都知道水果不但為我們提供豐富的膳食纖維,還有維生素及其它營養。例如:吃草莓培養耐心補充維生素,吃香蕉保持快樂心情,吃葡萄增強免疫抗衰老,吃梨幫助器
C#之裝箱和拆箱
簡介: 我們都知道,C#中的每一種型別要麼是值型別,要麼是引用型別。所以每個物件要麼是值型別的例項,要麼是引用型別的例項。 值型別和引用型別之間的轉換稱為裝箱和拆箱。
java 之裝箱與拆箱的研究總結
今天看java JDK7的新特新語法糖的時候突然有提到裝箱與拆箱,就特地加深一下: 所謂裝箱:就是在程式碼編譯期將基本型別轉化為包裝型別,使其基本有型別具有物件特徵 所謂拆箱:與裝箱過程相反,就是把包裝型別轉為基本型別 自動裝箱/拆箱 就是程式碼在編譯根據程式碼的語法,
深入理解Java之裝箱與拆箱
### 一、Java資料型別 1、在說裝箱與拆箱之前,先說一下Java的基本資料型別,Java從資料型別上可以劃分為`值型別`與`引用型別`,值型別是`四類八種`,分別是: + 整數型:byte̵,short̵,int̵,long + 浮點型:float,double + 字元型:char + 布林型:bo
Java學習筆記之——自動裝箱與拆箱
自動裝箱與拆箱 基本型別與引用型別的互相轉換 1. 基本型別對應的包裝類 byte short char int &
2018年11月13日Java學習之包裝類,裝箱與拆箱,
1.將八種基本資料型別定義相應的引用型別—包裝類 這樣做的好處就是可以呼叫類的方法了。 2.裝箱與拆箱 裝箱 int i = 500; Integer t = new Integer(i); 拆箱 boolean b = bObj.booleanValue
C#裝箱與拆箱
將值型別轉換為引用型別的過程叫做裝箱,相反,將引用型別轉換為值型別的過程叫做拆箱。 裝箱 : 裝箱允許將值型別隱式轉換為引用型別 從程式結果來看,值型別變數的值複製到裝箱得到的物件中,裝箱後改變值型別變數的值,並不會影響裝箱物件的值
4.3 詳解Java的自動裝箱與拆箱 > 我的程式猿之路:第三十三章
1 //自動裝箱 2 Integer total = 99; 3 4 //自定拆箱 5 int totalprim = total; 簡單一點說,裝箱就是自動將基本資料型別轉換為包裝器型別;拆箱就是自動將包裝器型別轉換為基本資料型別。 下面我們來看看需要裝箱拆箱的型別有哪些: 這個過程是自動執
C#泛型List< >集合:建立、與陣列轉換,記錄執行時間、裝箱與拆箱、dictionary
List<int > li=new List<int>();//建立泛型集合 List<int>與陣列存放的型別都是固定的,但集合的長度是任意改變的,陣列的大小是固定的。當變數的數量不確定時,採用集合 //陣列
C#裝箱與拆箱簡單理解(個人筆記)
簡例: int i = 1; //值型別 object obj = i; //引用型別 這是一個裝箱的過程,是將值型別轉換為引用型別的過程 int i =1; object obj = i; int x = (int)obj; //轉換int型別賦值給x 這
C# 裝箱與拆箱轉換
style 拆箱 裝箱 對象 esp class except read main 一、裝箱轉換(boxing) 裝箱時一種隱式轉換,它接受值類型的值,根據這個值在堆上創建一個完整的引用類型類型對象並返回對象引用,簡單來說就是將值類型轉換為引用類型 任何值類型Val
【C#基礎】裝箱與拆箱
由於C#中所有資料型別都是基類System.Object繼承而來,所以值型別和引用型別的值可以通過顯示(或隱式)操作相互轉換,而這轉換的過程也就是裝箱(boxing)和拆箱(un
[DotNet]深入理解C#的裝箱和拆箱
csharp 簡單 部分 nbsp pre 需要 避免 兩個 value 裝箱和拆箱是值類型和引用類型之間相互轉換是要執行的操作。 1. 裝箱在值類型向引用類型轉換時發生 2. 拆箱在引用類型向值類型轉換時發生 光上述兩句話不難理解,但是往深處了解,就需要一些篇幅來
裝箱與拆箱的整理
影響 配對 垃圾 多態 one member 轉換 追加 ++ 1、概念:裝箱是將值類型裝換成引用類型的過程;拆箱就是將引用類型轉換成值類型的過程; 2、利用裝箱和拆箱功能,通過允許值類型的任何值與Object類型的值進行相互轉換,將引用 類型與值類型連接起來。 3、
Java中自動裝箱與拆箱詳解
sans 做的 sys 實例 代碼 而在 byte 裝箱 bsp 在講裝箱與拆箱之前我們要先了解一下這個問題的來源: Java中的類型分為基本類型(Primitive type)和類類型(Class type)兩種: 基本類型包括byte型、char型、short型
詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)
初始 BE 運算 null 異常 內存 判斷 運行 double 一、什麽是自動裝箱拆箱 很簡單,下面兩句代碼就可以看到裝箱和拆箱過程 1 //自動裝箱 2 Integer total = 99; 3 4 //自定拆箱 5 int totalprim = total;
java中的裝箱與拆箱
net chan import HA com 裝箱 ava art spa java中的裝箱與拆箱 ,參考這三個博客,寫的很好 1 http://www.importnew.com/15712.html 2 https://www.cnblogs.com/dol
日期類時間類,日期時間類,單例模式,裝箱與拆箱,數字類隨機數,BigDecimal總結
方便 下標 時分秒 etime 相等 創建 rep style with 1.日期類,時間類,日期時間類 初步日期使用方法及格式轉換方法(舊方法): 格式://Mon Jul 30 11:26:05 CST 2018 年月日時分秒 CST代表北