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


124

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

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

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

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

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

Console.WriteLine(b.ToString());

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


27
Обратите внимание, что вы всегда можете проверить, действительно ли значение, с помощью Enum.IsDefined .
— Тим Шмелтер

круто, я этого не знал, вероятно, буду использовать его в проблеме, которую пытаюсь решить сейчас
— jcvandan

4
Но также прочтите это: goldfishforthought.blogspot.com/2008/03/…
— Тим Шмелтер

1
Проголосовали за то, что Стелла стала в два раза дороже Бада.
— Дэн

Ответы:


81

Взято из путаницы с анализом 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. Это позволяет иметь перечисления «битовых флагов», которые могут использовать двоичные шаблоны для определения, активен ли конкретный «флаг» в значении перечисления. Если вы должны были определить все возможные комбинации флагов (то есть MondayAndTuesday, MondayAndWednesdayAndThursday), это было бы крайне утомительно. Так что возможность использовать неопределенные значения перечисления может быть очень кстати. Это просто требует небольшой дополнительной работы, если вам нужно безотказное поведение для типов перечислений, которые не используют такого рода уловки.


Приятно ... Я тоже не фанат того, как это работает, мне это кажется странным
— jcvandan

3
@dormisher: "Это безумие, но есть способ этого не делать". Смотрите мою правку.
— StriplingWarrior

2
@StriplingWarrior, для интереса. Эквивалент в Java - EnumSet.of (понедельник, среда, четверг). Внутри EnumSet всего один long. Это дает хороший API без особой потери эффективности.
— Iain


@StriplingWarrior в своем методе синтаксического анализа вы пытаетесь вызвать IsDefined как метод расширения. Однако метод IsDefined нельзя сделать методом расширения из-за того, как вы пытаетесь создать класс EnumUtil с ограничениями (чтобы выполнить статическую проверку перечисления, что мне очень нравится). однако на самом деле он говорит мне, что нельзя создать метод расширения для универсального класса. Любые идеи?
— CJC

55

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

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

Permission p = Permission.Read | Permission.Write;

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

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


19
Но 3 "явно является допустимым значением" как бы усиливает точку зрения ОП. Атрибут [Flags] может указать компилятору искать определенное перечисление или допустимое значение. Просто говорю'.
— LarsTech

15

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

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

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

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

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


Вам это кажется странным? Я думал, что одним из основных моментов перечисления является безопасная группировка диапазона целых чисел, которым мы также можем приписывать индивидуальные значения. Для меня разрешение типу перечисления содержать любое целочисленное значение лишает его цели.
— jcvandan

@dormisher: Я только что отредактировал и добавил немного в конце. Обеспечение необходимой вам «безопасности» требует определенных затрат. Тем не менее, при нормальном использовании это действительно не проблема.
— Reed Copsey

9

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.