intがC#の正当な列挙型であるかどうかを確認する方法はありますか?


167

私はいくつかのSOの投稿を読みましたが、最も基本的な操作が欠落しているようです。

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

これにより例外は発生しません78。保存しても問題ありません。列挙型に入る値を検証する方法はありますか?


回答:


271

Enum.IsDefinedを確認する

使用法:

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

これはそのページの例です:

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

この例では、次の出力が表示されます。

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False

@matti:「78」をLoggingLevelストレージとして使用する任意の数値表現に変換し、それをLoggingLevel列挙値として提示します。
thecoop 2010

9
思われるIsDefinedビット演算列挙メンバーのために働いていません。
Saeed Neamati

29

上記の解決策は[Flags]状況を扱いません。

以下の私の解決策にはいくつかのパフォーマンスの問題があるかもしれませんが(さまざまな方法で最適化できると確信しています)、本質的には常に列挙値が有効かどうかを証明します。

それは3つの仮定に依存しています:

  • C#のEnum値はのみ許可されint、それ以外は絶対に許可されません
  • C#の列挙名は英字で始まる必要あります
  • 有効な列挙名にマイナス記号を付けることはできません: -

ToString()列挙型を呼び出すと、int一致する列挙型(フラグかどうか)がない場合は値が返されます。許可された列挙値が一致する場合、一致の名前を出力します。

そう:

[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"

これらの2つのルールを念頭に置いて、.NET Frameworkが正しく機能すれば、有効な列挙型のToString()メソッドを呼び出すと、最初の文字がアルファベットの文字になると想定できます。

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

これを「ハック」と呼ぶこともできますが、Microsoft独自の実装EnumとC#標準に依存することで、バグのある可能性のある独自のコードやチェックに依存しないという利点があります。パフォーマンスがそれほど重要でない状況では、これにより多くの厄介なswitchステートメントやその他のチェックが節約されます!

編集する

元の実装では負の値がサポートされていなかったことを指摘してくれた@ChaseMedallionに感謝します。これは修正され、テストが提供されました。

そしてそれをバックアップするテスト:

[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());
    }
}

1
このおかげで、有効なフラグの組み合わせを処理する同様の問題が発生しました。列挙型の最初の文字をチェックする代わりに、int.TryParse(enumValue.ToString())...を試すこともできます。失敗した場合は、有効なフラグのセットがあります。ただし、これは実際にはソリューションよりも遅い場合があります。
MadHenchbot 2014

チェックは数字以外の文字をチェックするため、この実装は負の値を正しく検証できません
ChaseMedallion

グッドキャッチ!! 私はそのようなことに対応するために私の回答を更新します。ありがとう@ChaseMedallion
joshcomley

私はこのソリューションが一番好き[Flags]です。提示された数学のトリックは、適切な整数値がある場合にのみ機能します。
MrLore、

17

正解はになりますがEnum.IsDefined、それはa:タイトなループで使用すると少し遅い、b:[Flags]列挙型には役に立たない

個人的には、私はそれについて心配するのをやめ、ちょうどswitch適切に覚えています:

  • すべてを認識しなくてもよい(そして何もしない)場合は、を追加しないでくださいdefault:(またはdefault:理由を説明する空欄を用意しないでください)。
  • 適切なデフォルトの動作がある場合は、それを default:
  • そうでなければ、あなたが知っているものを処理し、残りのために例外を投げます:

そのようです:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}

[フラグ]列挙型とパフォーマンスに精通していないので、あなたの答えは列挙型が最初に発明された理由のようです;)「ポイント」またはそれらが呼び出されているものを見ると、そこにポイントが必要です。何もせずに取得できなかったに違いないが、1つの列挙型定義に257個の値がある構成ファイルを読み取る状況について考えてみてください。数十の他の列挙型はもちろんです。多くのケース行があります...
char m

@matti-それは極端な例に聞こえます。逆シリアル化はとにかく専門分野です-ほとんどのシリアル化エンジンは列挙型検証を無料で提供します。
Marc Gravell

@matti-余談です。答えはそれぞれのメリットに基づいて扱います。私は時々完全に間違ったものになり、 "rep 17"の誰かが同じように完璧な答えを出すことができました。
Marc Gravell

スイッチの答えは速いですが、一般的ではありません。
Eldritch Conundrum、2011年

8

使用する:

Enum.IsDefined ( typeof ( Enum ), EnumValue );


4

対処するために、C#クックブックからこのソリューションを[Flags]使用することもできます。

まず、ALL列挙型に新しい値を追加します。

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

次に、値が次の場所にあるかどうかを確認しますALL

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

2

そのための1つの方法は、キャストと列挙型から文字列への変換に依存することです。intをEnum型にキャストすると、intは対応するenum値に変換されるか、enum値がintに定義されていない場合、結果のenumには値としてintのみが含まれます。

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

エッジケースについてはテストされていません。


1

他の人が言ったように、で装飾された列挙型のビットフラグの有効な組み合わせがある場合でもEnum.IsDefined戻ります。falseFlagsAttribute

悲しいことに、有効なビットフラグに対してtrueを返すメソッドを作成する唯一の方法は、少し長いです。

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

結果をGetCustomAttribute辞書にキャッシュしたい場合があります:

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

上記のコードは、C#7.3以降でのみ利用可能な新しいEnum制約を使用していることに注意してくださいTobject value古いバージョンのを渡して呼び出す必要がありGetType()ます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.