Effective C# Item17:盡量減少裝箱和拆箱

來源:互聯網
上載者:User

    裝箱和拆箱存在的意義:實值型別是資料的容器,它儲存在堆棧上,不具備多態性,而.NET架構在整個對象層次的設計中,使用System.Object作為所有類型的基類,但是Obejct是參考型別,而作為實值型別的基類System.ValueType,是從System.Object派生出來的,這就產生了矛盾,裝箱和拆箱就是為瞭解決這兩種類型之間的差異。

    裝箱會將一個實值型別放入一個未具名類型(untyped)的引用對象中,從而允許該實值型別應用於那些只能使用參考型別的場合。拆箱則會從前面的裝箱對象中提取出一個實值型別的副本。裝箱和拆箱都是比較耗時的操作。

    裝箱操作會將實值型別轉換為一個參考型別,這個過程中會建立一個新的引用獨享,然後將其分配到堆上,同時實值型別的副本會被儲存在該引用對象內部。當我們需要從裝箱對象中擷取任何資訊時,會建立實值型別的一個副本,然後將其返回。其中的關鍵是:當我們需要參考型別時,會建立一個新的參考型別的對象並將其放入到堆中;當我們需要訪問已經裝箱的對象資訊時,就會建立對應實值型別的一個副本,並將其返回。

    裝箱和拆箱最大的問題是它們會自動發生。當我們使用的是實值型別,而期望的是參考型別,那麼編譯器就會自動產生裝箱和拆箱語句。

   我們來看下面的語句,居然也發生了裝箱和拆箱操作。

Console.WriteLine("A few numbers:{0}, {1}, {2}",
25, 32, 50);

    上述代碼之所以發生了裝箱,是因為WriteLine方法需要的參數類型是System.Object,而25是一個int類型,屬於實值型別,因此需要裝箱,而在WriteLine方法內部實現時,需要調用方法參數的ToString()方法,為了調用裝箱對象的方法,就會發生拆箱的操作。

    為了避免裝箱和拆箱,可以將上述代碼進行如下修改。

Console.WriteLine("A few numbers:{0}, {1}, {2}",
25.ToString(), 32.ToString(), 50.ToString());

    另外,由於裝箱和拆箱都會產生新的執行個體,那麼有時會產生一些詭異的bug,我們來查看下面的代碼。

代碼

 1 public struct Person
2 {
3 private string _Name;
4
5 public string Name
6 {
7 get
8 {
9 return _Name;
10 }
11 set
12 {
13 _Name = value;
14 }
15 }
16
17 public override string ToString( )
18 {
19 Return _Name;
20 }
21 }
22
23  // Using the Person in a collection:
24 ArrayList attendees = new ArrayList( );
25 Person p = new Person( "Old Name" );
26 attendees.Add( p );
27
28 // Try to change the name:
29 // Would work if Person was a reference type.
30 Person p2 = (( Person )attendees[ 0 ] );
31 p2.Name = "New Name";
32
33 // Writes "Old Name":
34 Console.WriteLine(
35 attendees[ 0 ].ToString( ));

    上述代碼中,Person是一個實值型別,在將其放入ArrayList時,會進行裝箱操作,這時會有一次複製操作,當我們需要獲得ArrayList內Person對象的資訊時,需要一次拆箱,又會有一次複製操作,因此,當我們並沒有對ArrayList內的對象進行修改,而是針對副本進行修改。

    我們可以通過以下的方式來修改上述代碼存在的問題。

代碼

 1 public interface IPersonName
2 {
3 string Name
4 {
5 get; set;
6 }
7 }
8
9 struct Person : IPersonName
10 {
11 private string _Name;
12
13 public string Name
14 {
15 get
16 {
17 return _Name;
18 }
19 set
20 {
21 _Name = value;
22 }
23 }
24
25 public override string ToString( )
26 {
27 return _Name;
28 }
29 }
30
31 // Using the Person in a collection:
32 ArrayList attendees = new ArrayList( );
33 Person p = new Person( "Old Name" );
34 attendees.Add( p ); // box
35
36 // Try to change the name:
37 // Use the interface, not the type.
38 // No Unbox needed
39 (( IPersonName )attendees[ 0 ] ).Name = "New Name";
40
41 // Writes "New Name":
42 Console.WriteLine(
43 attendees[ 0 ].ToString( )); // unbox
44
45

    裝箱後的參考型別實現了原來實值型別對象上所有的介面,這意味著不會再發生複製,但是當我們調用IPersonName.Name屬性時,它會將調用請求轉寄給“箱子”內部的實值型別,在實值型別上實現介面使我們可以訪問”箱子“的內部,從而允許直接改變ArrayList中的資訊。

 

   總之,我們應該對任何將實值型別轉換為System.Object或者介面類型的構造保持密切的關注,例如將實值型別放入集合中,在實值型別上調用System.Object定義的方法等,這些操作都會將實值型別轉換為System.Object,只要有可能,我們都應該避免這種轉換。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.