Dot-Net

枚舉如何從 System.Enum 派生並且同時是整數?

  • January 7, 2011

編輯:底部的評論。另外,.


這就是讓我感到困惑的地方。我的理解是,如果我有這樣的枚舉……

enum Animal
{
   Dog,
   Cat
}

…我基本上所做的是定義了一個用兩個定義的值呼叫的值類型,並且. 這種類型派生自引用類型(值類型通常不能做的事情——至少在 C# 中是不允許的——但在這種情況下是允許的),並且具有來迴轉換到值/從值轉換的功能。Animal``Dog``Cat System.Enum``int

如果我剛才描述的枚舉類型是真的,那麼我希望下面的程式碼會拋出一個InvalidCastException

public class Program
{
   public static void Main(string[] args)
   {
       // Box it.
       object animal = Animal.Dog;

       // Unbox it. How are these both successful?
       int i = (int)animal;
       Enum e = (Enum)animal;

       // Prints "0".
       Console.WriteLine(i);

       // Prints "Dog".
       Console.WriteLine(e);
   }
}

通常,您不能將值類型從System.Object它的確切類型以外的任何東西拆箱。那麼以上怎麼可能呢?就好像該Animal類型同時是一個int(不僅僅是可轉換int)並且Enum不僅僅是可轉換Enum)。是多重繼承嗎?是否System.Enum以某種方式繼承自System.Int32(我沒想到會發生的事情)?

編輯:它不能是上述任何一個。以下程式碼最終證明了這一點(我認為):

object animal = Animal.Dog;

Console.WriteLine(animal is Enum);
Console.WriteLine(animal is int);

上述輸出:

真的
錯誤的

MSDN 關於枚舉的文件和 C# 規範都使用了術語“底層類型”;但我不知道這是什麼意思,我也從未聽說過它用於指代枚舉以外的任何東西。“基礎類型”實際上是什麼意思


那麼,這是否是另一個從 CLR 獲得特殊處理的案例

我的錢是在這種情況下……但答案/解釋會很好。


更新Damien_The_Unbeliever提供了真正回答這個問題的參考。可以在 CLI 規範的第 II 部分中的枚舉部分中找到解釋:

出於綁定目的(例如,為了從用於呼叫它的方法引用中定位方法定義),枚舉應與它們的底層類型不同。對於所有其他目的,包括驗證和執行程式碼,未裝箱的枚舉可以自由地與其基礎類型相互轉換。枚舉可以被裝箱到對應的裝箱實例類型,但是這種類型和底層類型的裝箱類型一樣,所以裝箱不會失去枚舉的原始類型。

編輯(再次?!):等等,實際上,我不知道我第一次讀對了。也許它並不能 100% 解釋專門的拆箱行為本身(儘管我將 Damien 的回答視為已接受,因為它對這個問題有很大的了解)。我會繼續研究這個…


另一個編輯:伙計,然後yodaj007 的回答讓我陷入了另一個循環。不知何故,枚舉與 ; 並不完全相同int。仍然int可以將 an 分配給沒有強制轉換的枚舉變數嗎?嗯?

我認為這一切最終都被漢斯的回答所闡明,這就是我接受它的原因。(對不起,達米安!)

是的,特殊待遇。JIT 編譯器敏銳地意識到裝箱值類型的工作方式。這通常是使值類型表現得有點分裂的原因。裝箱涉及創建一個 System.Object 值,該值的行為方式與引用類型的值完全相同。那時,值類型值的行為不再像執行時的值那樣。例如,這使得擁有像 ToString() 這樣的虛擬方法成為可能。裝箱的對像有一個方法表指針,就像引用類型一樣。

JIT 編譯器預先知道 int 和 bool 等值類型的方法表指針。對它們進行裝箱和拆箱非常有效,只需要少量機器程式碼指令。這需要在 .NET 1.0 中保持高效以使其具有競爭力。一個其中重要的部分是值類型值只能拆箱為相同類型的限制。這避免了不得不生成呼叫正確轉換程式碼的大量 switch 語句的抖動。它所要做的就是檢查對像中的方法表指針並驗證它是否是預期的類型。並直接將值從對像中複製出來。值得注意的是,VB.NET 中不存在這種限制,它的 CType() 運算符實際上會為包含這個大 switch 語句的輔助函式生成程式碼。

Enum 類型的問題是這不起作用。枚舉可以有不同的 GetUnderlyingType() 類型。換句話說,未裝箱的值具有不同的大小,因此簡單地將值從裝箱對像中複製出來是行不通的。敏銳地意識到,抖動不再內聯拆箱程式碼,它會在 CLR 中生成對輔助函式的呼叫。

該助手名為 JIT_Unbox(),您可以在 SSCLI20 原始碼 clr/src/vm/jithelpers.cpp 中找到它的原始碼。您將看到它專門處理枚舉類型。它是允許的,它允許從一種枚舉類型拆箱到另一種枚舉類型。但僅當基礎類型相同時,如果不是這種情況,您會收到 InvalidCastException。

這也是 Enum 被聲明為類的原因。它的邏輯行為是引用類型,派生的枚舉類型可以從一個轉換到另一個。上面提到的對底層類型兼容性的限制。然而,枚舉類型的值具有值類型值的行為。它們具有複製語義和裝箱行為。

引用自:https://stackoverflow.com/questions/4626394