web-dev-qa-db-ja.com

「var」を使用すると、Enum.GetValues()が名前を返すのはなぜですか?

誰かがこれを説明できますか?

代替テキスト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
    }
}
29
Edward Tanguay

_Enum.GetValues_はArrayを返すものとして宣言されています。
返される配列には、実際の値がReportStatus値として含まれています。

したがって、varキーワードはobjectになり、value変数は(ボックス化された)型付き列挙値を保持します。
_Console.WriteLine_呼び出しは、オブジェクトに対してobjectを取りToString()を呼び出すオーバーロードに解決されます。オブジェクトは列挙型の場合は名前を返します。

intを反復処理すると、コンパイラは暗黙的に値をintにキャストし、value変数は通常の(およびボックス化されていない)int値を保持します。 。
したがって、_Console.WriteLine_呼び出しは、intを取得して出力するオーバーロードに解決されます。

intDateTime(またはその他のタイプ)に変更しても、コンパイルは続行されますが、実行時にInvalidCastExceptionがスローされます。

42
SLaks

MSDNドキュメント によると、objectを取る_Console.WriteLine_のオーバーロードは、引数でToStringを内部的に呼び出します。

foreach (var value in ...)を実行すると、value変数はobjectとして入力されます(なぜなら、 SLaksが指摘しているように_Enum.GetValues_ は型指定されていないArray)を返すため、_Console.WriteLine_は_object.ToString_を呼び出しています。これは_System.Enum.ToString_によってオーバーライドされます。そして、このメソッドは列挙型のnameを返します。

foreach (int value in ...)を実行すると、列挙値が(intではなく)object値にキャストされます。したがって、_Console.WriteLine_は_System.Int32.ToString_を呼び出しています。

5
Dan Tao

FWIW、これがEnum.GetValues()からの逆アセンブルされたコードです(Reflector経由):

_[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;
}
_

varobjectであり、object.ToString()を呼び出して名前を返すことについて誰もが言っていることは正しいようです...

3
Tim Coker

Console.WriteLineを使用するときは、各要素で暗黙的にToString()を呼び出します。

そして、(明示的な型を使用して)intが必要だと言うと、それはintに変換され、次にToString()に変換されます。

最初のものは列挙値ToString() 'edです

2
Goblin

編集:配列を反復処理する多くの(おそらくすべて?)可能な方法を調査するサンプルコードを追加しました。

列挙型は、デフォルトでintから「派生」していると見なされます。必要に応じて、byte、short、longなどの他の整数タイプのいずれかから派生させることを選択できます。

どちらの場合も、Enum.GetValuesを呼び出すと、ReportStatusオブジェクトの配列が返されます。

最初のループでvarキーワードを使用すると、コンパイラーは、指定されたタイプの配列(ReportStatus)を使用して、値変数のタイプを判別するように指示されます。列挙型のToString実装は、列挙型エントリの名前を返すことであり、それが表す整数値ではありません。そのため、名前は最初のループから出力されます。

2番目のループでint変数を使用すると、Enum.GetValuesによって返される値が暗黙的にReportStatusからintに変換されます。もちろん、intでToStringを呼び出すと、整数値を表す文字列が返されます。暗黙の変換は、動作の違いを引き起こすものです。

更新:他の人が指摘しているように、Enum.GetValues関数は、Arrayとして型指定されたオブジェクトを返します。その結果、ReportStatus型ではなく、Object型の列挙型になります。

とにかく、Arrayまたは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の実装に依存すると思います。とにかく、詳細は多かれ少なかれ正しいと思います。

列挙型は整数とは異なります。サンプルでは、​​varはintに評価されず、列挙型に評価されます。列挙型自体を使用した場合も同じ出力が得られます。

列挙型は、値ではなく、出力時に名前を出力します。

0
Shirik

_var value_は実際には(ReportStatus型の)列挙値であるため、enumValue.ToString()の標準的な動作(名前)がわかります。

編集:
Console.WriteLine(value.GetType())を実行すると、プレーンなObjectで囲まれていますが、実際には 'ReportStatus'であることがわかります。

0
Hans Kesting