簡體   English   中英

為什么結構需要裝箱?

[英]Why do structs need to be boxed?

在C#中,任何用戶定義的struct都自動成為 System.Struct System.ValueType System.Struct 的子類System.ValueTypeSystem.Object的子類。

但是當我們為對象類型引用分配一些結構時,它會被裝箱。 例如:

struct A
{
    public int i;
}

A a;
object obj = a;  // boxing takes place here

所以我的問題是:如果ASystem.Object的后代,編譯器不能將它上傳到對象類型而不是裝箱嗎?

結構是一種值類型。 System.Object是引用類型。 運行時以不同方式存儲和處理值類型和引用類型。 要將值類型視為引用類型,必須將其設置為加框。 從低級別的角度來看,這包括將值從最初所在的堆棧復制到堆上新分配的內存,該內存還包含一個對象頭。 引用類型需要額外的頭來解析它們的vtable以啟用虛擬方法調度和其他引用類型相關的功能(請記住,堆棧上的結構只是一個值而且它沒有類型信息;它不包含任何類似vtable和can的內容不能直接用於解析動態調度的方法。 此外,要將某些東西視為引用類型,您必須有一個引用 (指針),而不是它的原始值。

所以我的問題是 - 如果A是System.Object的后代,是不是可以將它編譯為對象類型而不是拳擊?

在較低級別,值不會繼承任何內容。 實際上,正如我之前所說,它並不是一個真正的對象。 A派生自System.ValueType ,而System.ValueType又派生自System.Object是在編程語言(C#)的抽象級別定義的,而C#確實很好地隱藏了裝箱操作。 您沒有明確提及任何內容來設置值,因此您可以簡單地認為編譯器已為您“上傳”了該結構。 它使得值的繼承和多態的錯覺 ,而多態行為所需的工具都不是由它們直接提供的。

這是我更喜歡考慮它的方式。 考慮包含32位整數的變量的實現。 當被視為值類型時,整個值適合32位存儲。 這就是值類型:存儲只包含構成值的位,僅此而已。

現在考慮包含對象引用的變量的實現。 該變量包含一個“引用”,可以通過多種方式實現。 它可以是垃圾收集器結構的句柄,也可以是托管堆上的地址,或者其他什么。 但它可以讓你找到一個對象。 這就是引用類型:與引用類型變量關聯的存儲包含一些允許引用對象的位。

顯然,這兩件事完全不同。

現在假設你有一個object類型的變量,並且你希望將int類型變量的內容復制到其中。 你怎么做呢? 構成整數的32位不是這些“引用”之一,它只是一個包含32位的桶。 引用可以是進入托管堆的64位指針,也可以是垃圾收集器數據結構中的32位句柄,或者您可以想到的任何其他實現,但32位整數只能是32位整數。

那么你在這種情況下所做的就是選中整數:你創建一個包含整數存儲的新對象,然后存儲對新對象的引用。

只有當你想要(1)擁有統一的類型系統,並且(2)確保32位整數消耗32位內存時,才需要拳擊。 如果你願意拒絕其中任何一個,那么你就不需要拳擊; 我們不願意拒絕那些,所以拳擊是我們被迫忍受的。

雖然.NET的設計者當然不需要包含拳擊部分4.3的C#語言規范很好解釋了它背后的意圖,IMO:

裝箱和拆箱可以實現類型系統的統一視圖,其中任何類型的值最終都可以被視為對象。

因為值類型不是引用類型(最終是System.Object),所以存在裝箱行為以便具有統一類型系統,其中任何東西的值可以表示為對象。

這與C ++不同,其中類型系統不統一,所有類型都沒有通用的基本類型。

“如果struct ASystem.Object的后代,那么編譯器不能將它上傳而不是裝箱嗎?”

不,僅僅因為根據C#語言的定義,在這種情況下“up-casting” 裝箱。

C#的語言規范包含(在第13章中)所有可能的類型轉換的目錄。 所有這些轉換都以特定方式進行分類(例如數字轉換,參考轉換等)。

  1. 存在從類型S到其超類型T隱式類型轉換,但這些轉換僅針對“從類類型S到引用類型T的模式定義。 由於struct A不是類類型,因此無法在示例中應用這些轉換。

    也就是說, A ((間接)從object派生(雖然正確)這一事實在這里簡直無關緊要。 相關的是A是結構值類型。

  2. 與模式“從值類型A到其引用超類型object匹配的唯一現有轉換被歸類為裝箱轉換。 因此,從structobject每次轉換根據定義都被認為是裝箱。

struct是一種設計的值類型,因此在轉換為引用類型時需要加框。 struct派生自System.ValueType ,它在術語派生自System.Object

結構對象后代這一事實並不意味着......因為CLR在運行時處理的structs與引用類型不同。

在問題得到解答之后,我將提出與該主題相關的一些“技巧”:

struct s可以實現接口。 如果將值類型傳遞給期望此值類型實現的接口的函數,則該值通常會被裝箱。 使用泛型你可以避免拳擊:

interface IFoo {...}
struct Bar : IFoo {...}

void boxing(IFoo x) { ... }
void byValue<T>(T x) : where T : IFoo { ... }

var bar = new Bar();
boxing(bar);
byValue(bar);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM