Dlaczego rzutowanie int na nieprawidłową wartość wyliczenia NIE zgłasza wyjątku?


124

Jeśli mam takie wyliczenie:

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

Dlaczego nie zgłasza wyjątku podczas rzutowania wartości intspoza tych wartości na typ Beer?

Na przykład poniższy kod nie zgłasza wyjątku, wysyła wartość „50” do konsoli:

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

Console.WriteLine(b.ToString());

Uważam to za dziwne ... czy ktoś może wyjaśnić?


27
Zauważ, że zawsze możesz sprawdzić, czy wartość jest prawidłowa za pomocą Enum.IsDefined .
Tim Schmelter

fajne Nie wiedziałem, prawdopodobnie wykorzystam to w problemie, który obecnie próbuję rozwiązać
jcvandan


1
Głos za to, że Stella jest warta dwa razy więcej niż Bud.
Dan Bechard

Odpowiedzi:


81

Zaczerpnięte z zamieszania z parsowaniem wyliczenia

To była decyzja ludzi, którzy stworzyli .NET. Enum jest wspierany przez innego typu wartości ( int, short,byte , etc), a więc może faktycznie mieć dowolną wartość, która jest ważna dla tych typów wartości.

Osobiście nie jestem fanem tego, jak to działa, więc stworzyłem szereg metod użytkowych:

/// <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);
    }
}

W ten sposób mogę powiedzieć:

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

... lub:

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

Edytować

Poza wyjaśnieniem podanym powyżej, musisz zdać sobie sprawę, że wersja .NET Enum jest zgodna ze wzorcem bardziej inspirowanym C niż inspirowanym Javą. To sprawia, że ​​jest to możliwe „Bit Flag”, które mogą używać wzorców binarnych do określenia, czy dana „flaga” jest aktywna w wartości wyliczenia. Gdybyś miał określić każdą możliwą kombinację flag (tj MondayAndTuesday, MondayAndWednesdayAndThursday), to byłoby niezwykle uciążliwe. Tak więc możliwość używania niezdefiniowanych wartości wyliczeniowych może być naprawdę przydatna. Wymaga to tylko trochę dodatkowej pracy, gdy chcesz uzyskać szybkie zachowanie na typach wyliczeniowych, które nie wykorzystują tego rodzaju sztuczek.


Fajnie ... Nie jestem też fanem tego, jak to działa, wydaje mi się dziwne
jcvandan

3
@dormisher: "To szaleństwo, ale jest na to sposób." Zobacz moją edycję.
StriplingWarrior

2
@StriplingWarrior, dla zainteresowania. Odpowiednikiem Javy jest EnumSet.of (poniedziałek, środa, czwartek). Wewnątrz EnumSet jest tylko jeden długi. Daje więc ładne API bez dużej utraty wydajności.
Iain


@StriplingWarrior w metodzie analizy, którą próbujesz wywołać IsDefined jako metodę rozszerzającą. Jednak metoda IsDefined nie może być metodą rozszerzającą ze względu na sposób, w jaki próbujesz utworzyć klasę EnumUtil z ograniczeniami (aby wykonać statyczne sprawdzenie enum, co bardzo mi się podoba). jednakże w efekcie mówi mi, że nie można utworzyć metody rozszerzającej na klasie generycznej. Jakieś pomysły?
CJC

55

Wyliczenia są często używane jako flagi:

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

Permission p = Permission.Read | Permission.Write;

Wartość p to liczba całkowita 3, która nie jest wartością wyliczenia, ale z pewnością jest prawidłową wartością.

Osobiście wolałbym zobaczyć inne rozwiązanie; Wolałbym mieć możliwość tworzenia liczb całkowitych typu „tablica bitowa” i typu „zestaw odrębnych wartości” jako dwóch różnych funkcji językowych, zamiast łączenia ich obu w „wyliczenie”. Ale to właśnie wymyślili projektanci oryginalnego języka i frameworka; w rezultacie musimy pozwolić, aby niezadeklarowane wartości wyliczenia były wartościami prawnymi.


19
Ale 3 „oczywiście to ważna wartość” w pewnym sensie wzmacnia punkt widzenia PO. Ten atrybut [Flags] może powiedzieć kompilatorowi, aby szukał zdefiniowanego wyliczenia lub prawidłowej wartości. Tylko mówię'.
LarsTech

15

Krótka odpowiedź: projektanci języka postanowili zaprojektować język w ten sposób.

Długa odpowiedź: Section 6.2.2: Explicit enumeration conversionsspecyfikacji języka C # mówi:

Jawna konwersja wyliczenia między dwoma typami jest przetwarzana przez traktowanie dowolnego uczestniczącego typu wyliczeniowego jako typu bazowego tego typu wyliczeniowego, a następnie wykonywanie niejawnej lub jawnej konwersji liczbowej między wynikowymi typami. Na przykład, biorąc pod uwagę typ wyliczeniowy E z i bazowy typ int, konwersja z E na bajt jest przetwarzana jako jawna konwersja numeryczna (§6.2.1) z int na bajt, a konwersja z bajtu na E jest przetwarzana jako niejawna konwersja numeryczna (§6.1.2) z bajtu na int.

Zasadniczo wyliczenie jest traktowane jako typ podstawowy, jeśli chodzi o wykonanie operacji konwersji. Domyślnie podstawowym typem wyliczenia jest Int32, co oznacza, że ​​konwersja jest traktowana dokładnie tak, jak konwersja do Int32. Oznacza to, że intdopuszczalna jest każda prawidłowa wartość.

Podejrzewam, że zrobiono to głównie ze względu na wydajność. Tworząc enumprosty typ całkowity i zezwalając na dowolną konwersję typu całkowitego, środowisko CLR nie musi wykonywać wszystkich dodatkowych kontroli. Oznacza to, że użycie enumzmiennej tak naprawdę nie powoduje utraty wydajności w porównaniu z użyciem liczby całkowitej, co z kolei zachęca do jej używania.


Czy uważasz to za dziwne? Pomyślałem, że jednym z głównych punktów wyliczenia jest bezpieczne grupowanie zakresu liczb całkowitych, którym możemy również przypisać indywidualne znaczenia. Dla mnie zezwolenie typowi wyliczeniowemu na przechowywanie dowolnej liczby całkowitej jest sprzeczne z jego celem.
jcvandan

@dormisher: Właśnie zredagowałem i dodałem trochę na końcu. Zapewnienie „bezpieczeństwa”, którego szukasz, wiąże się z pewnymi kosztami. Biorąc to pod uwagę, przy normalnym użytkowaniu nie stanowi to żadnego problemu.
Reed Copsey

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.