Почему приведение int к недопустимому значению перечисления НЕ выбрасывает исключение?

Если у меня есть перечисление вроде так:

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

Почему он не генерирует исключение при приведении int, который находится за пределами этих значений, к типу Beer?

Например, следующий код не генерирует исключение, он выводит "50" на консоль:

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

Console.WriteLine(b.ToString());

Я нахожу это странным... может ли кто-нибудь уточнить?

Ответ 1

Взято из Путаница с разбором Enum

Это было решение со стороны людей, создавших .NET. Перечисление поддерживается другим типом значения (int, short, byte и т.д.), И поэтому он может фактически иметь любое значение, которое действительно для этих типов значений.

Я лично не поклонник того, как это работает, поэтому я сделал ряд полезных методов:

/// <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.

Изменить

Помимо приведенного выше объяснения, вы должны понимать, что версия Enum.NET соответствует более C-вдохновленному шаблону, чем Java-вдохновленный. Это позволяет иметь "Bit Flag" перечисления, которые могут использовать двоичные паттерны для определения того, активен ли определенный флаг в значении перечисления. Если вам нужно было определить все возможные комбинации флагов (т.е. MondayAndTuesday, MondayAndWednesdayAndThursday), это было бы очень утомительно. Поэтому возможность использования значений undefined enum может быть очень удобной. Это просто требует немного дополнительной работы, когда вам нужно быстрое поведение по типам перечислений, которые не используют эти трюки.

Ответ 2

Перечисления часто используются в качестве флагов:

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

Permission p = Permission.Read | Permission.Write;

Значение p является целым числом 3, которое не является значением перечисления, но явно является допустимым значением.

Я лично предпочел бы увидеть другое решение; Я предпочел бы иметь возможность создавать "битовые массивы" целочисленных типов и "набор различных значений" как две разные языковые функции, а не объединять их в "перечисление". Но это то, что придумали оригинальный язык и дизайнеры рамок; в результате мы должны разрешить не объявленные значения перечисления как легальные значения.

Ответ 3

Короткий ответ: языковые дизайнеры решили создать язык таким образом.

Длинный ответ: Section 6.2.2: Explicit enumeration conversions Спецификации языка С# говорит:

Явное преобразование перечислений между двумя типами обрабатывается путем обработки любого участвующего типа перечисления в качестве базового типа этого типа перечисления, а затем выполнения неявного или явного числового преобразования между результирующими типами. Например, с учетом типа E с перечислением и базового типа int преобразование из E в байт обрабатывается как явное числовое преобразование (§6.2.1) от int до байта, а преобразование из байта в E обрабатывается как неявное числовое преобразование (§6.1.2) от байта к int.

В принципе, перечисление рассматривается как основной тип, когда дело доходит до операции преобразования. По умолчанию базовый тип перечисления - Int32, что означает, что преобразование обрабатывается точно так же, как преобразование в Int32. Это означает, что допустимое значение int допустимо.

Я подозреваю, что это было сделано главным образом по соображениям производительности. Сделав enum простой интегральный тип и разрешив любое интегральное преобразование типа, CLR не нужно выполнять все дополнительные проверки. Это означает, что использование enum действительно не имеет потери производительности по сравнению с использованием целого числа, что, в свою очередь, способствует его использованию.

Ответ 4

В документации :

Переменная типа Days может быть назначено любое значение в диапазоне базовый тип; значения не ограниченные именованными константами.