Mengapa mentransmisikan int ke nilai enum yang tidak valid TIDAK memunculkan pengecualian?


124

Jika saya memiliki enum seperti ini:

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

Mengapa tidak memunculkan pengecualian saat mentransmisikan intyang di luar nilai-nilai ini ke jenis Beer?

Misalnya, kode berikut tidak menampilkan pengecualian, ia mengeluarkan '50' ke konsol:

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

Console.WriteLine(b.ToString());

Saya merasa ini aneh ... adakah yang bisa menjelaskan?


27
Perhatikan bahwa Anda selalu dapat memeriksa apakah suatu nilai valid dengan Enum.IsDefined .
Tim Schmelter

keren Saya tidak tahu itu, mungkin akan menggunakannya dalam masalah yang sedang saya coba selesaikan
jcvandan


1
Dipilih karena membuat Stella bernilai dua kali lipat Bud.
Dan Bechard

Jawaban:


81

Diambil dari Confusion dengan mengurai Enum

Ini adalah keputusan dari pihak orang-orang yang membuat .NET. Enum didukung oleh jenis nilai lain ( int, short, byte, dll), dan sehingga benar-benar dapat memiliki nilai yang berlaku untuk jenis-jenis nilai.

Saya pribadi bukan penggemar cara kerjanya, jadi saya membuat serangkaian metode utilitas:

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

Dengan cara ini, saya dapat mengatakan:

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

... atau:

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

Sunting

Di luar penjelasan yang diberikan di atas, Anda harus menyadari bahwa versi .NET dari Enum mengikuti pola yang lebih terinspirasi C daripada yang terinspirasi oleh Java. Hal ini memungkinkan untuk memiliki enum "Bit Flag" yang dapat menggunakan pola biner untuk menentukan apakah "flag" tertentu aktif dalam nilai enum. Jika Anda harus menentukan setiap kemungkinan kombinasi flag (mis MondayAndTuesday. MondayAndWednesdayAndThursday), Ini akan sangat membosankan. Jadi memiliki kapasitas untuk menggunakan nilai enum yang tidak ditentukan bisa sangat berguna. Ini hanya membutuhkan sedikit kerja ekstra ketika Anda menginginkan perilaku cepat gagal pada jenis enum yang tidak memanfaatkan trik semacam ini.


Bagus ... Saya juga bukan penggemar cara kerjanya, tampak aneh bagi saya
jcvandan

3
@dormisher: "'Ini kegilaan, namun ada metode untuk tidak melakukannya." Lihat suntingan saya.
StriplingWarrior

2
@StriplingWarrior, untuk minat. Setara dengan Java adalah EnumSet.of (Senin, Rabu, Kamis). Di dalam EnumSet hanya satu panjang. Jadi memberikan API yang bagus tanpa banyak kehilangan efisiensi.
Iain


@StriplingWarrior dalam metode parse, Anda mencoba memanggil IsDefined seperti metode ekstensi. Namun metode IsDefined tidak dapat dijadikan metode ekstensi karena cara Anda mencoba membuat kelas EnumUtil dengan batasan (untuk membuat pemeriksaan statis untuk enum, yang sangat saya sukai). namun pada dasarnya ia memberi tahu saya bahwa seseorang tidak dapat membuat metode ekstensi pada kelas generik. Ada ide?
CJC

55

Enum sering digunakan sebagai bendera:

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

Permission p = Permission.Read | Permission.Write;

Nilai p adalah bilangan bulat 3, yang bukan merupakan nilai enum, tetapi jelas merupakan nilai yang valid.

Saya pribadi lebih suka melihat solusi yang berbeda; Saya lebih suka memiliki kemampuan untuk membuat tipe integer "bit array" dan "satu set nilai yang berbeda" sebagai dua fitur bahasa yang berbeda daripada menggabungkan keduanya menjadi "enum". Tapi itulah yang dihasilkan oleh desainer bahasa dan kerangka kerja asli; Akibatnya, kami harus mengizinkan nilai yang tidak dideklarasikan dari enum menjadi nilai hukum.


19
Tapi 3 "jelas merupakan nilai yang valid" semacam memperkuat poin OP. Atribut [Flags] itu bisa memberi tahu compiler untuk mencari enum yang ditentukan atau nilai yang valid. Katakan saja.
LarsTech

15

Jawaban singkatnya: Para desainer bahasa memutuskan untuk mendesain bahasa dengan cara ini.

Jawaban panjang: Section 6.2.2: Explicit enumeration conversionsSpesifikasi Bahasa C # mengatakan:

Konversi pencacahan eksplisit antara dua tipe diproses dengan memperlakukan tipe enum yang berpartisipasi sebagai tipe yang mendasari tipe enum tersebut, dan kemudian melakukan konversi numerik implisit atau eksplisit antara tipe yang dihasilkan. Misalnya, diberikan enum-type E dengan dan tipe int yang mendasari, konversi dari E ke byte diproses sebagai konversi numerik eksplisit (§6.2.1) dari int ke byte, dan konversi dari byte ke E diproses sebagai konversi numerik implisit (§6.1.2) dari byte ke int.

Pada dasarnya, enum diperlakukan sebagai tipe yang mendasari saat melakukan operasi konversi. Secara default, jenis yang mendasari enum adalah Int32, yang berarti konversi diperlakukan persis seperti konversi ke Int32. Ini berarti intnilai yang valid diperbolehkan.

Saya menduga ini dilakukan terutama karena alasan kinerja. Dengan membuat enumtipe integral sederhana dan mengizinkan semua konversi tipe integral, CLR tidak perlu melakukan semua pemeriksaan tambahan. Ini berarti bahwa menggunakan an enumtidak benar-benar kehilangan kinerja jika dibandingkan dengan menggunakan integer, yang pada gilirannya membantu mendorong penggunaannya.


Apakah menurut Anda ini aneh? Saya pikir salah satu poin utama enum adalah mengelompokkan berbagai bilangan bulat dengan aman sehingga kita juga dapat mengatribusikan arti individual. Bagi saya mengizinkan tipe enum untuk menahan nilai integer apa pun yang mengalahkan tujuannya.
jcvandan

@dormisher: Saya baru saja mengedit dan menambahkan sedikit di bagian akhir. Ada biaya yang harus dikeluarkan untuk menyediakan "keamanan" yang Anda cari. Karena itu, dengan penggunaan normal , ini sama sekali bukan masalah.
Reed Copsey

9

Dari dokumentasi :

Variabel jenis Hari dapat diberi nilai apa pun dalam kisaran jenis yang mendasarinya; nilainya tidak terbatas pada konstanta bernama.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.