Apakah ada cara untuk memeriksa apakah int adalah enum legal dalam C #?

167

Saya sudah membaca beberapa posting SO dan sepertinya sebagian besar operasi dasar hilang.

public enum LoggingLevel
{
    Off = 0,
    Error = 1,
    Warning = 2,
    Info = 3,
    Debug = 4,
    Trace = 5
};

if (s == "LogLevel")
{
    _log.LogLevel = (LoggingLevel)Convert.ToInt32("78");
    _log.LogLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), "78");
    _log.WriteDebug(_log.LogLevel.ToString());
}

Ini tidak menyebabkan pengecualian, senang menyimpannya 78. Apakah ada cara untuk memvalidasi nilai yang masuk ke enum?

char m
sumber
2
Kemungkinan duplikat Nilai Validasi Enum
Erik

Jawaban:

271

Lihat Enum. Sudah ditentukan

Pemakaian:

if(Enum.IsDefined(typeof(MyEnum), value))
    MyEnum a = (MyEnum)value; 

Ini adalah contoh dari halaman itu:

using System;    
[Flags] public enum PetType
{
   None = 0, Dog = 1, Cat = 2, Rodent = 4, Bird = 8, Reptile = 16, Other = 32
};

public class Example
{
   public static void Main()
   {
      object value;     
      // Call IsDefined with underlying integral value of member.
      value = 1;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with invalid underlying integral value.
      value = 64;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with string containing member name.
      value = "Rodent";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with a variable of type PetType.
      value = PetType.Dog;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = PetType.Dog | PetType.Cat;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with uppercase member name.      
      value = "None";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = "NONE";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with combined value
      value = PetType.Dog | PetType.Bird;
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = value.ToString();
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
   }
}

Contoh ini menampilkan output berikut:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False
SwDevMan81
sumber
@matti: Konversikan "78" menjadi representasi angka apa pun yang LoggingLeveldigunakan sebagai penyimpanan, lalu sajikan sebagai LoggingLevelnilai enum.
thecoop
9
Tampaknya itu IsDefinedtidak berfungsi untuk anggota enum bitwised.
Saeed Neamati
29

Solusi di atas tidak berurusan dengan [Flags]situasi.

Solusi saya di bawah ini mungkin memiliki beberapa masalah kinerja (saya yakin orang dapat mengoptimalkan dengan berbagai cara) tetapi pada dasarnya itu akan selalu membuktikan apakah nilai enum valid atau tidak .

Itu bergantung pada tiga asumsi:

  • Nilai enum dalam C # hanya diizinkan int, sama sekali bukan yang lain
  • Nama enum dalam C # harus dimulai dengan karakter alfabet
  • Tidak boleh ada nama enum yang valid dengan tanda minus: -

Memanggil ToString()enum mengembalikan intnilai jika tidak ada enum (ditandai atau tidak) yang cocok. Jika nilai enum yang diizinkan cocok, itu akan mencetak nama yang cocok.

Begitu:

[Flags]
enum WithFlags
{
    First = 1,
    Second = 2,
    Third = 4,
    Fourth = 8
}

((WithFlags)2).ToString() ==> "Second"
((WithFlags)(2 + 4)).ToString() ==> "Second, Third"
((WithFlags)20).ToString() ==> "20"

Dengan mengingat dua aturan ini, kita dapat mengasumsikan bahwa jika .NET Framework melakukan tugasnya dengan benar, setiap panggilan ke ToString()metode enum yang valid akan menghasilkan sesuatu yang memiliki karakter alfabet sebagai karakter pertama:

public static bool IsValid<TEnum>(this TEnum enumValue)
    where TEnum : struct
{
    var firstChar = enumValue.ToString()[0];
    return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}

Orang bisa menyebutnya "retas", tetapi keuntungannya adalah dengan mengandalkan implementasi Enumdan standar C # Microsoft, Anda tidak mengandalkan kode atau cek yang berpotensi bermasalah. Dalam situasi di mana kinerja tidak terlalu kritis, ini akan menghemat banyak switchpernyataan jahat atau cek lainnya!

Edit

Terima kasih kepada @ChaseMedallion untuk menunjukkan bahwa implementasi asli saya tidak mendukung nilai negatif. Ini telah diperbaiki dan tes disediakan.

Dan tes untuk mendukungnya:

[TestClass]
public class EnumExtensionsTests
{
    [Flags]
    enum WithFlags
    {
        First = 1,
        Second = 2,
        Third = 4,
        Fourth = 8
    }

    enum WithoutFlags
    {
        First = 1,
        Second = 22,
        Third = 55,
        Fourth = 13,
        Fifth = 127
    }

    enum WithoutNumbers
    {
        First, // 1
        Second, // 2
        Third, // 3
        Fourth // 4
    }

    enum WithoutFirstNumberAssigned
    {
        First = 7,
        Second, // 8
        Third, // 9
        Fourth // 10
    }


    enum WithNagativeNumbers
    {
        First = -7,
        Second = -8,
        Third = -9,
        Fourth = -10
    }

    [TestMethod]
    public void IsValidEnumTests()
    {
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4 | 2)).IsValid());
        Assert.IsTrue(((WithFlags)(2)).IsValid());
        Assert.IsTrue(((WithFlags)(3)).IsValid());
        Assert.IsTrue(((WithFlags)(1 + 2 + 4 + 8)).IsValid());

        Assert.IsFalse(((WithFlags)(16)).IsValid());
        Assert.IsFalse(((WithFlags)(17)).IsValid());
        Assert.IsFalse(((WithFlags)(18)).IsValid());
        Assert.IsFalse(((WithFlags)(0)).IsValid());

        Assert.IsTrue(((WithoutFlags)1).IsValid());
        Assert.IsTrue(((WithoutFlags)22).IsValid());
        Assert.IsTrue(((WithoutFlags)(53 | 6)).IsValid());   // Will end up being Third
        Assert.IsTrue(((WithoutFlags)(22 | 25 | 99)).IsValid()); // Will end up being Fifth
        Assert.IsTrue(((WithoutFlags)55).IsValid());
        Assert.IsTrue(((WithoutFlags)127).IsValid());

        Assert.IsFalse(((WithoutFlags)48).IsValid());
        Assert.IsFalse(((WithoutFlags)50).IsValid());
        Assert.IsFalse(((WithoutFlags)(1 | 22)).IsValid());
        Assert.IsFalse(((WithoutFlags)(9 | 27 | 4)).IsValid());

        Assert.IsTrue(((WithoutNumbers)0).IsValid());
        Assert.IsTrue(((WithoutNumbers)1).IsValid());
        Assert.IsTrue(((WithoutNumbers)2).IsValid());
        Assert.IsTrue(((WithoutNumbers)3).IsValid());
        Assert.IsTrue(((WithoutNumbers)(1 | 2)).IsValid()); // Will end up being Third
        Assert.IsTrue(((WithoutNumbers)(1 + 2)).IsValid()); // Will end up being Third

        Assert.IsFalse(((WithoutNumbers)4).IsValid());
        Assert.IsFalse(((WithoutNumbers)5).IsValid());
        Assert.IsFalse(((WithoutNumbers)25).IsValid());
        Assert.IsFalse(((WithoutNumbers)(1 + 2 + 3)).IsValid());

        Assert.IsTrue(((WithoutFirstNumberAssigned)7).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)8).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)9).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)10).IsValid());

        Assert.IsFalse(((WithoutFirstNumberAssigned)11).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)6).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(7 | 9)).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(8 + 10)).IsValid());

        Assert.IsTrue(((WithNagativeNumbers)(-7)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-8)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-9)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-10)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(-11)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(7)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(8)).IsValid());
    }
}
Joshcomley
sumber
1
Terima kasih untuk ini, saya memiliki masalah serupa yang berhubungan dengan kombinasi flag yang valid. Sebagai alternatif untuk memeriksa karakter pertama enum, Anda juga dapat mencoba int.TryParse (enumValue.ToString ()) ... Jika gagal, Anda memiliki serangkaian flag yang valid. Ini sebenarnya mungkin lebih lambat dari solusi Anda.
MadHenchbot
Implementasi ini gagal memvalidasi nilai negatif dengan benar, karena cek adalah untuk karakter non-digit
ChaseMedallion
Tangkapan yang bagus!! Saya akan memperbarui jawaban saya untuk mengakomodasi hal tersebut, terima kasih @ChaseMedallion
joshcomley
Saya suka solusi ini yang terbaik, trik matematika yang disajikan hanya berfungsi jika [Flags]memiliki nilai integer yang masuk akal.
MrLore
17

Jawaban kanoniknya adalah Enum.IsDefined, tetapi itu adalah: agak lambat jika digunakan dalam loop ketat, dan b: tidak berguna untuk [Flags]enum.

Secara pribadi, saya akan berhenti mengkhawatirkan hal itu, dan dengan switchtepat, mengingat:

  • jika OK untuk tidak mengenali segalanya (dan tidak melakukan apa-apa), maka jangan tambahkan default:(atau kosongkan default:penjelasan mengapa)
  • jika ada perilaku default yang masuk akal, masukkan itu ke dalam default:
  • jika tidak, tangani yang Anda tahu dan lontarkan pengecualian untuk sisanya:

Seperti itu:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}
Marc Gravell
sumber
tidak akrab dengan enum [Bendera] dan kinerja bukanlah masalah sehingga jawaban Anda sepertinya adalah alasan mengapa enum ditemukan pertama kali;) melihat "poin" Anda atau apa pun namanya mereka sehingga Anda harus ada benarnya di sana . Taruhan Anda tidak mendapatkannya secara gratis, tetapi pikirkan situasi membaca file konfigurasi di mana ada 257 nilai dalam satu definisi enum. Apalagi puluhan enum lainnya. Akan ada banyak baris kasus ...
char m
@matti - itu terdengar contoh ekstrem; deserialisasi adalah bidang spesialis - sebagian besar mesin serialisasi menawarkan validasi enum gratis.
Marc Gravell
@matti - di samping; Saya akan mengatakan memperlakukan jawaban berdasarkan pada kemampuan masing-masing. Kadang-kadang saya salah, dan seseorang dengan "perwakilan 17" juga bisa memberikan jawaban yang sempurna .
Marc Gravell
Jawaban peralihan cepat, tetapi tidak umum.
Eldritch Conundrum
8

Menggunakan:

Enum.IsDefined ( typeof ( Enum ), EnumValue );
n535
sumber
4

Untuk mengatasinya [Flags]Anda juga dapat menggunakan solusi ini dari C # Cookbook :

Pertama, tambahkan ALLnilai baru ke enum Anda:

[Flags]
enum Language
{
    CSharp = 1, VBNET = 2, VB6 = 4, 
    All = (CSharp | VBNET | VB6)
}

Kemudian, periksa apakah nilainya ada di ALL:

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}
Mario Levrero
sumber
2

Salah satu cara untuk melakukannya adalah dengan mengandalkan casting dan enum untuk konversi string. Ketika casting int ke tipe Enum int dikonversi menjadi nilai enum yang sesuai atau enum yang dihasilkan hanya berisi int sebagai nilai jika nilai enum tidak didefinisikan untuk int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();

Tidak diuji untuk setiap kasus tepi.

maulik13
sumber
1

Seperti yang dikatakan orang lain, Enum.IsDefinedkembali falsebahkan jika Anda memiliki kombinasi bit flag yang valid untuk enum yang dihiasi dengan FlagsAttribute.

Sayangnya, satu-satunya cara untuk membuat metode yang mengembalikan true untuk flag bit yang valid agak panjang:

public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    // For enums decorated with the FlagsAttribute, allow sets of flags.
    if (!valid && enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true)
    {
        long mask = 0;
        foreach (object definedValue in Enum.GetValues(enumType))
            mask |= Convert.ToInt64(definedValue);
        long longValue = Convert.ToInt64(value);
        valid = (mask & longValue) == longValue;
    }
    return valid;
}

Anda mungkin ingin men-cache hasil GetCustomAttributedalam kamus:

private static readonly Dictionary<Type, bool> _flagEnums = new Dictionary<Type, bool>();
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    if (!valid)
    {
        // For enums decorated with the FlagsAttribute, allow sets of flags.
        if (!_flagEnums.TryGetValue(enumType, out bool isFlag))
        {
            isFlag = enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true;
            _flagEnums.Add(enumType, isFlag);
        }
        if (isFlag)
        {
            long mask = 0;
            foreach (object definedValue in Enum.GetValues(enumType))
                mask |= Convert.ToInt64(definedValue);
            long longValue = Convert.ToInt64(value);
            valid = (mask & longValue) == longValue;
        }
    }
    return valid;
}

Perhatikan bahwa kode di atas menggunakan Enumbatasan baru Tyang hanya tersedia sejak C # 7.3. Anda harus mengirimkan object valueversi yang lebih lama dan memanggilnya GetType().

sinar
sumber