Warum löst das Umsetzen von int auf einen ungültigen Aufzählungswert KEINE Ausnahme aus?


122

Wenn ich so eine Aufzählung habe:

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

Warum wird keine Ausnahme ausgelöst int, wenn ein Wert außerhalb dieser Werte in einen Typ von umgewandelt wird Beer?

Der folgende Code löst beispielsweise keine Ausnahme aus, sondern gibt '50' an die Konsole aus:

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

Console.WriteLine(b.ToString());

Ich finde das seltsam ... kann jemand das klären?


27
Beachten Sie, dass Sie mit Enum.IsDefined jederzeit überprüfen können, ob ein Wert gültig ist .
Tim Schmelter

cool Ich wusste das nicht, werde es wahrscheinlich in dem Problem verwenden, das ich gerade zu lösen versuche
jcvandan


1
Upvoted dafür, dass Stella doppelt so viel wert ist wie Bud.
Dan Bechard

Antworten:


80

Entnommen aus der Verwirrung mit dem Parsen einer Aufzählung

Dies war eine Entscheidung der Leute, die .NET erstellt haben. Ein ENUM wird von einem anderen Werttyp gesichert ( int, short, byte, usw.), und so ist es tatsächlich einen beliebigen Wert haben kann , die für die Werttypen gültig ist.

Ich persönlich bin kein Fan der Funktionsweise, daher habe ich eine Reihe von Dienstprogrammmethoden entwickelt:

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

Auf diese Weise kann ich sagen:

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

... oder:

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

Bearbeiten

Über die oben gegebene Erklärung hinaus müssen Sie erkennen, dass die .NET-Version von Enum einem C-inspirierten Muster folgt als einem Java-inspirierten. Dies ermöglicht es, "Bit Flag" -Aufzählungen zu haben, die binäre Muster verwenden können, um zu bestimmen, ob ein bestimmtes "Flag" in einem Aufzählungswert aktiv ist. Wenn Sie jede mögliche Kombination von Flags (dh definieren haben MondayAndTuesday, MondayAndWednesdayAndThursday), würde dies äußerst langwierig sein. Die Fähigkeit, undefinierte Aufzählungswerte zu verwenden, kann also sehr praktisch sein. Es erfordert nur ein wenig zusätzliche Arbeit, wenn Sie ein schnelles Verhalten bei Aufzählungstypen wünschen, die diese Art von Tricks nicht nutzen.


Schön ... Ich bin auch kein Fan der Art und Weise, wie es funktioniert, scheint mir bizarr
jcvandan

3
@dormisher: "Es ist Wahnsinn, aber es gibt eine Methode, um nicht." Siehe meine Bearbeitung.
StriplingWarrior

2
@StriplingWarrior, für Interesse. Das Java-Äquivalent ist EnumSet.of (Montag, Mittwoch, Donnerstag). Innerhalb des EnumSet ist nur eine einzige lange. So ergibt sich eine schöne API ohne großen Effizienzverlust.
Iain


@StriplingWarrior In Ihrer Analysemethode versuchen Sie, IsDefined wie eine Erweiterungsmethode aufzurufen. Die IsDefined-Methode kann jedoch nicht zu einer Erweiterungsmethode gemacht werden, da Sie versuchen, die EnumUtil-Klasse mit Einschränkungen zu erstellen (um eine statische Prüfung auf Enum durchzuführen, was mir sehr gefällt). Tatsächlich sagt es mir jedoch, dass man für eine generische Klasse keine Erweiterungsmethode erstellen kann. Irgendwelche Ideen?
CJC

55

Aufzählungen werden häufig als Flags verwendet:

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

Permission p = Permission.Read | Permission.Write;

Der Wert von p ist die ganze Zahl 3, die kein Wert der Aufzählung ist, aber eindeutig ein gültiger Wert.

Ich persönlich hätte lieber eine andere Lösung gesehen; Ich hätte lieber die Möglichkeit gehabt, Ganzzahltypen "Bitarray" und "eine Reihe unterschiedlicher Werte" als zwei verschiedene Sprachmerkmale zu erstellen, anstatt beide zu "Enum" zusammenzufassen. Aber genau das haben sich die Designer der Originalsprache und des Frameworks ausgedacht. Infolgedessen müssen wir zulassen, dass nicht deklarierte Werte der Aufzählung legale Werte sind.


19
Aber 3 "eindeutig ist ein gültiger Wert" stärkt den Punkt des OP. Dieses Attribut [Flags] kann den Compiler anweisen, nach einer definierten Aufzählung oder einem gültigen Wert zu suchen. Ich sag bloß'.
LarsTech

15

Die kurze Antwort: Die Sprachdesigner haben beschlossen, die Sprache so zu gestalten.

Die lange Antwort: Section 6.2.2: Explicit enumeration conversionsder C # -Sprachenspezifikation lautet:

Eine explizite Aufzählungskonvertierung zwischen zwei Typen wird verarbeitet, indem jeder teilnehmende Aufzählungstyp als zugrunde liegender Typ dieses Aufzählungstyps behandelt und anschließend eine implizite oder explizite numerische Konvertierung zwischen den resultierenden Typen durchgeführt wird. Beispielsweise wird bei einem Aufzählungstyp E mit und einem zugrunde liegenden Typ von int eine Konvertierung von E nach Byte als explizite numerische Konvertierung (§6.2.1) von int nach Byte und eine Konvertierung von Byte nach E als verarbeitet eine implizite numerische Konvertierung (§6.1.2) von Byte nach Int.

Grundsätzlich wird die Aufzählung als zugrunde liegender Typ behandelt, wenn eine Konvertierungsoperation ausgeführt wird. Standardmäßig lautet der zugrunde liegende Typ einer Aufzählung. DiesInt32 bedeutet, dass die Konvertierung genau wie eine Konvertierung in behandelt wird Int32. Dies bedeutet, dass jeder gültige intWert zulässig ist.

Ich vermute, dass dies hauptsächlich aus Leistungsgründen gemacht wurde. Durch die Erstellung enumeines einfachen Integraltyps und die Ermöglichung einer Konvertierung des Integraltyps muss die CLR nicht alle zusätzlichen Überprüfungen durchführen. Dies bedeutet, dass die Verwendung von a im enumVergleich zur Verwendung einer Ganzzahl keinen wirklichen Leistungsverlust aufweist, was wiederum dazu beiträgt, die Verwendung zu fördern.


Findest du das seltsam? Ich dachte, einer der Hauptpunkte einer Aufzählung ist es, eine Reihe von ganzen Zahlen sicher zu gruppieren, denen wir auch individuelle Bedeutungen zuordnen können. Wenn ich zulasse, dass ein Aufzählungstyp einen ganzzahligen Wert enthält, ist dies für mich nicht sinnvoll.
JCVANDAN

@dormisher: Ich habe gerade etwas bearbeitet und am Ende etwas hinzugefügt. Die Bereitstellung der "Sicherheit", nach der Sie suchen, ist mit Kosten verbunden. Abgesehen davon ist dies bei normaler Verwendung überhaupt kein Problem.
Reed Copsey

9

Aus der Dokumentation :

Einer Variablen vom Typ Tage kann ein beliebiger Wert im Bereich des zugrunde liegenden Typs zugewiesen werden. Die Werte sind nicht auf die genannten Konstanten beschränkt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.