Perché la trasmissione di int a un valore enum non valido NON genera un'eccezione?


124

Se ho un'enumerazione come questa:

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

Perché non genera un'eccezione quando si esegue il cast di un messaggio intal di fuori di questi valori a un tipo di Beer?

Ad esempio, il codice seguente non genera un'eccezione, restituisce "50" alla console:

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

Console.WriteLine(b.ToString());

Lo trovo strano ... qualcuno può chiarire?


27
Nota che puoi sempre controllare se un valore è valido con Enum.IsDefined .
Tim Schmelter

cool Non lo sapevo, probabilmente lo userò nel problema che sto attualmente cercando di risolvere
jcvandan,


1
Votato per aver fatto sì che Stella valesse il doppio di Bud.
Dan Bechard

Risposte:


81

Tratto da Confusion con l'analisi di un Enum

Questa è stata una decisione da parte delle persone che hanno creato .NET. Un enum è sostenuta da un altro tipo di valore ( int, short, byte, ecc), e così si può effettivamente avere qualsiasi valore che è valido per i tipi di valore.

Personalmente non sono un fan del modo in cui funziona, quindi ho creato una serie di metodi di utilità:

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

In questo modo posso dire:

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

... o:

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

modificare

Al di là della spiegazione data sopra, devi rendersi conto che la versione .NET di Enum segue uno schema più ispirato al C rispetto a uno ispirato a Java. Ciò rende possibile avere enumerazioni "Bit Flag" che possono utilizzare modelli binari per determinare se un particolare "flag" è attivo in un valore enum. Se dovessi definire ogni possibile combinazione di flag (cioè MondayAndTuesday, MondayAndWednesdayAndThursday), questi sarebbero estremamente noiosi. Quindi avere la capacità di usare valori enum non definiti può essere davvero utile. Richiede solo un po 'di lavoro extra quando si desidera un comportamento rapido sui tipi di enumerazione che non sfruttano questo tipo di trucchi.


Bello ... nemmeno io sono un fan del modo in cui funziona, mi sembra strano
jcvandan,

3
@dormisher: "È una follia, eppure c'è un metodo per non farlo." Vedi la mia modifica.
StriplingWarrior

2
@StriplingWarrior, per interesse. L'equivalente Java è EnumSet.of (lunedì, mercoledì, giovedì). All'interno dell'EnumSet c'è solo un singolo lungo. Quindi offre una bella API senza molta perdita di efficienza.
Iain


@StriplingWarrior nel tuo metodo di analisi stai cercando di chiamare IsDefined come un metodo di estensione. Tuttavia il metodo IsDefined non può essere reso un metodo di estensione a causa del modo in cui si tenta di creare una classe EnumUtil con vincoli (per effettuare un controllo statico per enum, che mi piace molto). tuttavia in effetti mi sta dicendo che non è possibile creare un metodo di estensione su una classe generica. Qualche idea?
CJC

55

Gli enum sono spesso usati come flag:

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

Permission p = Permission.Read | Permission.Write;

Il valore di p è l'intero 3, che non è un valore dell'enumerazione, ma è chiaramente un valore valido.

Personalmente avrei preferito vedere una soluzione diversa; Avrei preferito avere la capacità di creare tipi interi "array di bit" e tipi "un insieme di valori distinti" come due diverse caratteristiche del linguaggio piuttosto che unirli entrambi in "enum". Ma questo è ciò che hanno escogitato i progettisti di linguaggio e framework originali; di conseguenza, dobbiamo consentire che i valori non dichiarati dell'enum siano valori legali.


19
Ma 3 "chiaramente è un valore valido" rafforza in qualche modo il punto di vista dell'OP. Quell'attributo [Flags] potrebbe dire al compilatore di cercare un'enumerazione definita o un valore valido. Sto solo dicendo.
LarsTech

15

La risposta breve: i progettisti del linguaggio hanno deciso di progettare il linguaggio in questo modo.

La risposta lunga: Section 6.2.2: Explicit enumeration conversionsdella specifica del linguaggio C # dice:

Una conversione di enumerazione esplicita tra due tipi viene elaborata trattando qualsiasi tipo di enumerazione partecipante come il tipo sottostante di tale tipo di enumerazione e quindi eseguendo una conversione numerica implicita o esplicita tra i tipi risultanti. Ad esempio, dato un tipo enum E con un tipo sottostante di int, una conversione da E a byte viene elaborata come una conversione numerica esplicita (§6.2.1) da int a byte, e una conversione da byte a E viene elaborata come una conversione numerica implicita (§6.1.2) da byte a int.

Fondamentalmente, l' enumerazione viene considerata come il tipo sottostante quando si tratta di eseguire un'operazione di conversione. Per default, un enum 's tipo sottostante è Int32, il che significa che la conversione è trattato esattamente come una conversione Int32. Ciò significa che intè consentito qualsiasi valore valido .

Sospetto che ciò sia stato fatto principalmente per motivi di prestazioni. Creando enumun tipo integrale semplice e consentendo qualsiasi conversione di tipo integrale, CLR non ha bisogno di eseguire tutti i controlli aggiuntivi. Ciò significa che l'utilizzo di un enumnon comporta alcuna perdita di prestazioni rispetto all'utilizzo di un numero intero, il che a sua volta aiuta a incoraggiarne l'uso.


Lo trovi strano? Ho pensato che uno dei punti principali di un'enumerazione fosse raggruppare in modo sicuro un intervallo di numeri interi a cui possiamo attribuire anche significati individuali. Per me, consentire a un tipo enum di contenere qualsiasi valore intero vanifica il suo scopo.
jcvandan

@dormisher: ho appena modificato e aggiunto un po 'alla fine. C'è un costo coinvolto nel fornire la "sicurezza" che stai cercando. Detto questo, con un utilizzo normale , questo non è affatto un problema.
Reed Copsey

9

Dalla documentazione :

A una variabile di tipo Days può essere assegnato qualsiasi valore compreso nell'intervallo del tipo sottostante; i valori non sono limitati alle costanti denominate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.