なぜintを無効な列挙値にキャストしても例外がスローされないのですか?


123

私がそのような列挙型を持っている場合:

enum Beer
{
    Bud = 10,
    Stella = 20,
    Unknown
}

intこれらの値の外にあるをの型にキャストするときに例外がスローされないのはなぜBeerですか?

たとえば、次のコードは例外をスローせず、コンソールに「50」を出力します。

int i = 50;
var b = (Beer) i;

Console.WriteLine(b.ToString());

私はこれが奇妙だと思います...誰かが明確にできますか?


27
Enum.IsDefinedを使用して、値が有効かどうかをいつでも確認できることに注意してください。
Tim Schmelter、2011年

クール私はそれを知らなかった、おそらく私が現在解決しようとしている問題でそれを使うでしょう
jcvandan '20

4
:しかし、また、この読みgoldfishforthought.blogspot.com/2008/03/...
ティムSchmelter

1
StellaをBudの2倍にする価値があると賛成。
Dan Bechard 2017年

回答:


81

列挙型の解析の混乱からの抜粋

これは、.NETを作成した人々の決定でした。列挙型は、別の値型(に裏付けされたintshortbyte、など)、そしてそれは実際にそれらの値タイプに対して有効な任意の値を持つことができます。

私は個人的にこれが機能する方法のファンではないので、私は一連のユーティリティメソッドを作りました:

/// <summary>
/// Utility methods for enum values. This static type will fail to initialize 
/// (throwing a <see cref="TypeInitializationException"/>) if
/// you try to provide a value that is not an enum.
/// </summary>
/// <typeparam name="T">An enum type. </typeparam>
public static class EnumUtil<T>
    where T : struct, IConvertible // Try to get as much of a static check as we can.
{
    // The .NET framework doesn't provide a compile-checked
    // way to ensure that a type is an enum, so we have to check when the type
    // is statically invoked.
    static EnumUtil()
    {
        // Throw Exception on static initialization if the given type isn't an enum.
        Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
    }

    /// <summary>
    /// In the .NET Framework, objects can be cast to enum values which are not
    /// defined for their type. This method provides a simple fail-fast check
    /// that the enum value is defined, and creates a cast at the same time.
    /// Cast the given value as the given enum type.
    /// Throw an exception if the value is not defined for the given enum type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumValue"></param>
    /// <exception cref="InvalidCastException">
    /// If the given value is not a defined value of the enum type.
    /// </exception>
    /// <returns></returns>
    public static T DefinedCast(object enumValue)

    {
        if (!System.Enum.IsDefined(typeof(T), enumValue))
            throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                           typeof (T).FullName);
        return (T) enumValue;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="enumValue"></param>
    /// <returns></returns>
    public static T Parse(string enumValue)
    {
        var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
        //Require that the parsed value is defined
        Require.That(parsedValue.IsDefined(), 
            () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                enumValue, typeof(T).FullName)));
        return parsedValue;
    }

    public static bool IsDefined(T enumValue)
    {
        return System.Enum.IsDefined(typeof (T), enumValue);
    }

}


public static class EnumExtensions
{
    public static bool IsDefined<T>(this T enumValue)
        where T : struct, IConvertible
    {
        return EnumUtil<T>.IsDefined(enumValue);
    }
}

このように、私は言うことができます:

if(!sEnum.IsDefined()) throw new Exception(...);

...または:

EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.

編集する

上記の説明に加えて、.NETバージョンのEnumは、Javaに触発されたパターンよりもCに触発されたパターンに従っていることを理解する必要があります。これにより、バイナリパターンを使用して特定の「フラグ」が列挙値でアクティブかどうかを判断できる「ビットフラグ」列挙型を使用できます。フラグの可能なすべての組み合わせ(つまりMondayAndTuesdayMondayAndWednesdayAndThursday)を定義する必要がある場合、これらは非常に面倒です。したがって、未定義の列挙値を使用する機能があると、非常に便利です。この種のトリックを利用しない列挙型のフェイルファスト動作が必要な場合は、少し余分な作業が必要です。


いい...私もそれが機能する方法のファンではない、私には奇妙に思われる
jcvandan

3
@dormisher:「これは狂気ですが、やる方法はあります。」私の編集を参照してください。
StriplingWarrior 2011年

2
@StriplingWarrior。対応するJavaはEnumSet.of(Monday、Tuesday、Tuesday)です。EnumSetの内部は単一のlongです。したがって、効率を大幅に損なうことなく、素晴らしいAPIを提供します。
イアン


拡張メソッドのようにIsDefinedを呼び出そうとしている解析メソッドの@StriplingWarrior。ただし、IsDefinedメソッドは、制約を使用してEnumUtilクラスを作成しようとする方法(列挙型の静的チェックを行うため)が原因で、拡張メソッドにできません。ただし、実際には、ジェネリッククラスで拡張メソッドを作成できないことがわかります。何か案は?
CJC 2016年

55

列挙型はしばしばフラグとして使用されます:

[Flags]
enum Permission
{
    None = 0x00,
    Read = 0x01,
    Write = 0x02,
}
...

Permission p = Permission.Read | Permission.Write;

pの値は整数3です。これは列挙型の値ではありませんが、明らかに有効な値です。

私は個人的には別の解決策を見たかったのです。「ビット配列」の整数型を作成する機能があったほうがいい「列挙型」に融合するのではなく、「一連の異なる値」型を2つの異なる言語機能として作成する機能があったほうがよかったのです。しかし、それが元の言語とフレームワークの設計者が思いついたものです。その結果、列挙型の宣言されていない値を正当な値にする必要があります。


19
しかし、3は「明らかに有効な値」であり、OPのポイントを強化します。その[Flags]属性は、定義された列挙型または有効な値を探すようコンパイラーに指示できます。言ってるだけ'。
LarsTech

15

短い答え:言語設計者は、この方法で言語を設計することにしました。

Section 6.2.2: Explicit enumeration conversionsC#言語仕様の長い答えは次のとおりです。

2つのタイプ間の明示的な列挙型変換は、関与する列挙型をその列挙型の基になる型として扱い、結果の型間で暗黙的または明示的な数値変換を実行することによって処理されます。たとえば、列挙型がEで、基になる型がintの場合、Eからバイトへの変換はintからバイトへの明示的な数値変換(§6.2.1)として処理され、バイトからEへの変換は次のように処理されます。バイトから整数への暗黙の数値変換(§6.1.2)。

基本的に、enumは、変換操作を行うときに、基本となる型として扱われます。デフォルトでは、列挙型の基礎となる型はInt32です。つまり、変換は、Int32ます。これは、任意の有効なint値が許容されることを意味します。

これは主にパフォーマンス上の理由で行われたと思います。enum単純な整数型を作成し、任意の整数型変換を許可することにより、CLRは追加のチェックをすべて行う必要がなくなります。これはenum、整数を使用する場合と比較して、を使用しても実際にはパフォーマンスが低下しないことを意味します。これにより、その使用が促進されます。


これは奇妙だと思いますか?列挙型の主なポイントの1つは、個々の意味も関連付けることができる整数の範囲を安全にグループ化することだと思いました。enum型が整数値を保持できるようにすることは、その目的に反します。
jcvandan 2011年

@dormisher:編集して、最後に少し追加しました。あなたが求めている「安全性」を提供するには、コストがかかります。そうは言っても、通常の使用方法では、これはまったく問題ありません。
リードコプシー、2011年

9

ドキュメントから:

Daysタイプの変数には、基になるタイプの範囲内の任意の値を割り当てることができます。値は名前付き定数に限定されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.