Чому кастинг int до недійсного значення enum НЕ кидає виняток?


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/…
Tim Schmelter

1
Запропоновано зробити Stella вартістю вдвічі більше, ніж Буд.
Ден Бешард

Відповіді:


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.

Редагувати

За викладеним вище поясненням, ви повинні усвідомити, що версія .NET Enum дотримується більш натхненного С, ніж натхненного Java. Це дає змогу мати переліки "Бітовий прапор", які можуть використовувати двійкові шаблони, щоб визначити, чи активний певний "прапор" у значенні enum. Якщо ви повинні були визначити всі можливі комбінації прапорів (тобто MondayAndTuesday, MondayAndWednesdayAndThursday), це було б вкрай виснажливо. Тож наявність здатності використовувати невизначені значення перерахувань може бути дуже зручною. Це просто вимагає трохи додаткової роботи, коли ви хочете невдалої поведінки на типи перерахунків, які не використовують ці види хитрощів.


Приємно ... Я теж не шанувальник того, як це працює, мені здається дивним
jcvandan

3
@dormisher: "" Це безумство, але існує метод ". Дивіться мою редакцію.
Стриптинг-воїн

2
@StriplingWarrior, за інтерес. Java-еквівалент - EnumSet.of (понеділок, середа, четвер). Всередині EnumSet - це лише одна длина. Це дає приємний API без великих втрат ефективності.
Iain


@StriplingWarrior у вашому методі розбору ви намагаєтеся викликати IsDefined як метод розширення. Однак метод IsDefined неможливо зробити методом розширення через те, як ви намагаєтеся зробити клас EnumUtil з обмеженнями (зробити статичну перевірку на перерахунок, що мені дуже подобається). однак насправді це говорить мені, що не можна робити метод розширення на загальному класі. Будь-які ідеї?
CJC

55

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

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

Permission p = Permission.Read | Permission.Write;

Значення р - ціле число 3, яке не є значенням перерахунку, але, очевидно, є дійсним значенням.

Я особисто хотів би побачити інше рішення; Я б швидше мав можливість складати цілі типи "бітовий масив" і "набір чітких значень" як дві різні мовні особливості, а не конфігурувати їх обох у "перерахунок". Але ось що придумали оригінальні дизайнери мови та рамок; в результаті ми повинні дозволити недекларованим значенням enum бути юридичними цінностями.


19
Але 3 "явно є дійсним значенням", це посилює точку OP. Цей атрибут [Flags] міг би вказати компілятору шукати визначений перерахунок або допустиме значення. Просто говорю'.
LarsTech

15

Коротка відповідь: Мовні дизайнери вирішили створити мову таким чином.

Довга відповідь: Section 6.2.2: Explicit enumeration conversionsспецифікація мови C # говорить:

Явне перерахування переліку між двома типами обробляється шляхом трактування будь-якого учасника enum-типу як базового типу цього enum-типу, а потім виконує неявне або явне числове перетворення між результуючими типами. Наприклад, з урахуванням enum-типу E з базовим типом int, перетворення з E в байт обробляється як явне числове перетворення (§6.2.1) з int в байт, а перетворення з байта в E обробляється як неявне числове перетворення (§6.1.2) з байта в int.

В основному, enum трактується як базовий тип, коли справа доходить до операції перетворення. За замовчуванням базовим типом є enumInt32 , що означає, що конверсія трактується точно так само, як і конверсія Int32. Це означає, що будь-яке дійсне intзначення допустиме.

Я підозрюю, що це робилося переважно з міркувань продуктивності. Роблячи enumпростий інтегральний тип і дозволяючи будь-яке інтегральне перетворення типів, CLR не потрібно робити усі додаткові перевірки. Це означає, що використання enumне справді втрати продуктивності порівняно з використанням цілого числа, що, в свою чергу, сприяє його використанню.


Чи вважаєте ви це дивним? Я подумав, що одним із головних моментів перерахунків є безпечне групування цілого числа цілих чисел, яким ми також можемо віднести окремі значення. Мені дозволяють enum типу проводити будь-яке ціле значення, перемагає його призначення.
jcvandan

@dormisher: Я щойно відредагував і додав трохи наприкінці. Забезпечення "безпеки", яке ви хочете, потребує витрат. При звичайному використанні це насправді зовсім не проблема.
Рід Копсей

9

З документації :

Змінній типу Days може бути призначено будь-яке значення в діапазоні основного типу; значення не обмежуються названими константами.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.