2010-07-09 60 views
27

任何人都可以解釋這一點嗎?爲什麼Enum.GetValues()在使用「var」時返回名稱?

alt text http://www.deviantsart.com/upload/g4knqc.png

using System; 

namespace TestEnum2342394834 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      //with "var" 
      foreach (var value in Enum.GetValues(typeof(ReportStatus))) 
      { 
       Console.WriteLine(value); 
      } 

      //with "int" 
      foreach (int value in Enum.GetValues(typeof(ReportStatus))) 
      { 
       Console.WriteLine(value); 
      } 

     } 
    } 

    public enum ReportStatus 
    { 
     Assigned = 1, 
     Analyzed = 2, 
     Written = 3, 
     Reviewed = 4, 
     Finished = 5 
    } 
} 
+0

當你將鼠標懸停在'var'上時,Visual Studio會說什麼類型? – ChrisF 2010-07-09 14:17:47

+0

不知道,但似乎非常有用! – sbenderli 2010-07-09 14:18:11

+0

@sbenderli - 我剛剛檢查過它是'System.Object',它可能會解釋一下這個區別。 – ChrisF 2010-07-09 14:23:12

回答

39

Enum.GetValues聲明爲返回Array
它返回的數組包含實際值爲ReportStatus值。

因此,var關鍵字變成object,並且value變量保存(盒裝)類型的枚舉值。
Console.WriteLine呼叫解析爲過載,該過載需要object並在對象上調用ToString(),對於枚舉,該過載返回名稱。

當遍歷一個int,編譯器隱式連鑄值int,並且value變量保存正常(和非盒裝)int值。
因此,Console.WriteLine呼叫解析爲需要int並打印的超載。

如果將int更改爲DateTime(或任何其他類型),它仍會編譯,但它會在運行時引發InvalidCastException

+0

因此,基礎IEnumerable返回依賴於隱式轉換,是嗎? – Andreas 2010-07-09 14:18:44

+3

好的解釋,+1。請注意,如果枚舉的基礎類型是另一個數字類型(例如uint或short),那麼第二個foreach也會失敗。 – 2010-07-09 14:30:08

+0

@Andreas,在第一個循環中沒有進行轉換,而在第二個循環中,只允許繼承鑄造。也就是說,並非隱式或明確定義的演員。 – Dykam 2010-07-09 15:09:33

5

根據the MSDN documentationConsole.WriteLine的過載需要object內部調用ToString的參數。

當你做foreach (var value in ...),你value變量的類型爲object(因爲,as SLaks points outEnum.GetValues返回一個類型化Array)等你Console.WriteLine呼籲object.ToString這是由System.Enum.ToString覆蓋。並且此方法返回枚舉的名稱

當你做foreach (int value in ...),你正在鑄造的枚舉值爲int值(而不是object);所以Console.WriteLineSystem.Int32.ToString

+0

是的,'foreach(ReportStatus'編譯和打印名字, – 2010-07-09 14:21:01

2

當您使用Console.WriteLine時,您隱式地在每個元素上調用ToString()。

當你說你想要一個int(使用顯式類型),它會將它轉換爲一個int - 然後ToString()它。

第一個是枚舉值的ToString()'編

0

枚舉類型是從一個不同的整數。在你的示例中,var不計算爲int,它計算爲枚舉類型。如果您使用枚舉類型本身,您將獲得相同的輸出。

枚舉類型在打印時輸出名稱,而不是它們的值。

+2

關閉,但'var'實際上不計算枚舉類型,它計算爲'object'(參見SLaks的答案) – 2010-07-09 14:21:09

+2

從技術上講,編譯器會根據SLaks指出的將對象評估爲對象,但是在運行時,該值是枚舉類型(儘管裝箱)。值被裝箱的事實與問題提出的行爲無關 – 2010-07-09 15:23:34

0

var value實際上是一個枚舉值(ReportStatus類型),因此您可以看到enumValue.ToString()的標準行爲 - 它是名稱。

編輯:
當你做一個Console.WriteLine(value.GetType()),你會發現它實際上是一個「ReportStatus」,儘管它在一個普通的Object盒裝真實。

+1

錯誤。在這裏變成'object' – SLaks 2010-07-09 14:21:09

+0

這個回答並沒有錯,雖然沒有SLaks的答案那麼徹底。值變量IS實際上是ReportStatus類型,它只是編譯器將var評估爲對象,因爲它無法確定Enum.GetValues返回的數組的具體類型,但是在運行時評估'(值爲ReportStatus)'結果爲true。 是否值變量是裝入ReportStatus的對象或者實際上是否爲ReportStatus實際上與被問到的行爲無關。 – 2010-07-09 15:27:39

3

FWIW,這裏是從Enum.GetValues的反彙編代碼()(通過反射鏡):

[ComVisible(true)] 
public static Array GetValues(Type enumType) 
{ 
    if (enumType == null) 
    { 
     throw new ArgumentNullException("enumType"); 
    } 
    if (!(enumType is RuntimeType)) 
    { 
     throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "enumType"); 
    } 
    if (!enumType.IsEnum) 
    { 
     throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType"); 
    } 
    ulong[] values = GetHashEntry(enumType).values; 
    Array array = Array.CreateInstance(enumType, values.Length); 
    for (int i = 0; i < values.Length; i++) 
    { 
     object obj2 = ToObject(enumType, values[i]); 
     array.SetValue(obj2, i); 
    } 
    return array; 
} 

貌似每個人都在說關於var作爲一個object,並呼籲object.ToString()返回名稱是否正確?

+0

現在有些s ## stirrer會發布反彙編代碼......無論如何。 – Mau 2010-07-09 14:31:23

+0

我的立場是,如果MS是反拆解,他們可以很容易地混淆圖書館... – 2010-07-09 14:35:53

1

編輯:添加了一些示例代碼,探討了許多(也許是所有?)遍歷數組的可能方式。

默認情況下,枚舉類型被認爲是從int中「派生」的。如果需要,您可以選擇從其他整數類型之一派生它,如字節,短,長等。

在這兩種情況下,對Enum.GetValues的調用都返回一個ReportStatus對象數組。

在第一個循環中使用var關鍵字告訴編譯器使用指定類型的數組ReportStatus來確定值變量的類型。枚舉的ToString實現是返回枚舉條目的名稱,而不是它所表示的整數值,這就是爲什麼從第一個循環輸出名稱的原因。

在第二個循環中使用int變量會導致由Enum.GetValues返回的值從ReportStatus隱式轉換爲int。在int上調用ToString當然會返回一個表示整數值的字符串。隱式轉換是導致行爲差異的原因。

更新:正如其他人指出的那樣,Enum.GetValues函數返回一個類型爲Array的對象,因此它是Object類型的枚舉類型,而不是ReportStatus類型的枚舉類型。

不管怎樣,最終的結果是一樣的是否遍歷數組或ReportStatus []:

class Program 
{ 
    enum ReportStatus 
    { 
     Assigned = 1, 
     Analyzed = 2, 
     Written = 3, 
     Reviewed = 4, 
     Finished = 5, 
    } 

    static void Main(string[] args) 
    { 
     WriteValues(Enum.GetValues(typeof(ReportStatus))); 

     ReportStatus[] values = new ReportStatus[] { 
      ReportStatus.Assigned, 
      ReportStatus.Analyzed, 
      ReportStatus.Written, 
      ReportStatus.Reviewed, 
      ReportStatus.Finished, 
     }; 

     WriteValues(values); 
    } 

    static void WriteValues(Array values) 
    { 
     foreach (var value in values) 
     { 
      Console.WriteLine(value); 
     } 

     foreach (int value in values) 
     { 
      Console.WriteLine(value); 
     } 
    } 

    static void WriteValues(ReportStatus[] values) 
    { 
     foreach (var value in values) 
     { 
      Console.WriteLine(value); 
     } 

     foreach (int value in values) 
     { 
      Console.WriteLine(value); 
     } 
    } 
} 

只是一些額外的樂趣,我已經添加下面展示迭代的幾種不同的方式在指定的一些代碼數組和foreach循環,包括詳細描述每種情況下發生了什麼的註釋。

class Program 
{ 
    enum ReportStatus 
    { 
     Assigned = 1, 
     Analyzed = 2, 
     Written = 3, 
     Reviewed = 4, 
     Finished = 5, 
    } 

    static void Main(string[] args) 
    { 
     Array values = Enum.GetValues(typeof(ReportStatus)); 

     Console.WriteLine("Type of array: {0}", values.GetType().FullName); 

     // Case 1: iterating over values as System.Array, loop variable is of type System.Object 
     // The foreach loop uses an IEnumerator obtained from System.Array. 
     // The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function. 
     // The value variable is passed to Console.WriteLine(System.Object). 
     // Summary: 0 box operations, 0 unbox operations, 1 usage of TypedReference 
     Console.WriteLine("foreach (object value in values)"); 
     foreach (object value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 2: iterating over values as System.Array, loop variable is of type ReportStatus 
     // The foreach loop uses an IEnumerator obtained from System.Array. 
     // The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function. 
     // The current value is immediatly unboxed as ReportStatus to be assigned to the loop variable, value. 
     // The value variable is then boxed again so that it can be passed to Console.WriteLine(System.Object). 
     // Summary: 1 box operation, 1 unbox operation, 1 usage of TypedReference 
     Console.WriteLine("foreach (ReportStatus value in values)"); 
     foreach (ReportStatus value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 3: iterating over values as System.Array, loop variable is of type System.Int32. 
     // The foreach loop uses an IEnumerator obtained from System.Array. 
     // The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function. 
     // The current value is immediatly unboxed as System.Int32 to be assigned to the loop variable, value. 
     // The value variable is passed to Console.WriteLine(System.Int32). 
     // Summary: 0 box operations, 1 unbox operation, 1 usage of TypedReference 
     Console.WriteLine("foreach (int value in values)"); 
     foreach (int value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 4: iterating over values as ReportStatus[], loop variable is of type System.Object. 
     // The foreach loop is compiled as a simple for loop; it does not use an enumerator. 
     // On each iteration, the current element of the array is assigned to the loop variable, value. 
     // At that time, the current ReportStatus value is boxed as System.Object. 
     // The value variable is passed to Console.WriteLine(System.Object). 
     // Summary: 1 box operation, 0 unbox operations 
     Console.WriteLine("foreach (object value in (ReportStatus[])values)"); 
     foreach (object value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 5: iterating over values as ReportStatus[], loop variable is of type ReportStatus. 
     // The foreach loop is compiled as a simple for loop; it does not use an enumerator. 
     // On each iteration, the current element of the array is assigned to the loop variable, value. 
     // The value variable is then boxed so that it can be passed to Console.WriteLine(System.Object). 
     // Summary: 1 box operation, 0 unbox operations 
     Console.WriteLine("foreach (ReportStatus value in (ReportStatus[])values)"); 
     foreach (ReportStatus value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 6: iterating over values as ReportStatus[], loop variable is of type System.Int32. 
     // The foreach loop is compiled as a simple for loop; it does not use an enumerator. 
     // On each iteration, the current element of the array is assigned to the loop variable, value. 
     // The value variable is passed to Console.WriteLine(System.Int32). 
     // Summary: 0 box operations, 0 unbox operations 
     Console.WriteLine("foreach (int value in (ReportStatus[])values)"); 
     foreach (int value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 7: The compiler evaluates var to System.Object. This is equivalent to case #1. 
     Console.WriteLine("foreach (var value in values)"); 
     foreach (var value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 8: The compiler evaluates var to ReportStatus. This is equivalent to case #5. 
     Console.WriteLine("foreach (var value in (ReportStatus[])values)"); 
     foreach (var value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 
    } 
} 

- 在上面的示例中更新了我的評論;通過雙重檢查,我發現System.Array.GetValue方法實際上使用TypedReference類來提取數組的元素並將其作爲System.Object返回。我原來寫道,那裏發生了一場拳擊比賽,但事實上並非如此。我不確定盒子操作的比較與對TypedReference.InternalToObject的調用的比較;我認爲這取決於CLR的實施。無論如何,我相信現在的細節或多或少都是正確的。

+0

最喜歡的其他答案錯誤。 'var'變成'object'。 – SLaks 2010-07-09 14:38:31

+1

當然,我對var變成對象的看法不正確,但值變量是枚舉值的對象實際上與行爲不同的問題無關。我添加了一個代碼示例,它演示了無論var是對象還是var是ReportStatus,行爲都是相同的。 – 2010-07-09 14:55:31

相關問題