241

I've read a few SO posts and it seems most basic operation is missing.

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

This causes no exceptions, it's happy to store 78. Is there a way to validate a value going into an enum?

DLeh
  • 23,806
  • 16
  • 84
  • 128
char m
  • 7,840
  • 14
  • 68
  • 117
  • 2
    Possible duplicate of [Validate Enum Values](http://stackoverflow.com/questions/13615/validate-enum-values) – Erik Oct 07 '15 at 18:41

9 Answers9

398

Check out Enum.IsDefined

Usage:

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

This is the example from that page:

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

The example displays the following output:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False
bluish
  • 26,356
  • 27
  • 122
  • 180
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
36

The above solutions do not deal with [Flags] situations.

My solution below may have some performance issues (I'm sure one could optimise in various ways) but essentially it will always prove whether an enum value is valid or not.

It relies on three assumptions:

  • Enum values in C# are only allowed to be int, absolutely nothing else
  • Enum names in C# must begin with an alphabetic character
  • No valid enum name can being with a minus sign: -

Calling ToString() on an enum returns either the int value if no enum (flag or not) is matched. If an allowed enum value is matched, it will print the name of the match(es).

So:

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

With these two rules in mind we can assume that if the .NET Framework does its job correctly that any calls to a valid enum's ToString() method will result in something that has an alphabetic character as its first character:

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

One could call it a "hack", but the advantages are that by relying on Microsoft's own implementation of Enum and C# standards, you're not relying on your own potentially buggy code or checks. In situations where performance is not exceptionally critical, this will save a lot of nasty switch statements or other checks!

Edit

Thanks to @ChaseMedallion for pointing out that my original implementation did not support negative values. This has been remedied and tests provided.

And the tests to back it up:

[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
  • 28,099
  • 24
  • 107
  • 147
  • 1
    Thanks for this, I had a similar issue dealing with valid flag combinations. As an alternative to checking the first character of the enum, you could also try to int.TryParse(enumValue.ToString())... If it fails, you have a valid set of flags. This might actually be slower than your solution, though. – MadHenchbot Aug 25 '14 at 19:24
  • This implementation fails to correctly validate negative values, since check is for non-digit characters – ChaseMedallion Jul 23 '15 at 12:14
  • Good catch!! I'll update my answer to accommodate such, thank you @ChaseMedallion – joshcomley Jul 23 '15 at 12:54
  • I like this solution the best, the math trick ones presented only work if the `[Flags]` have sensible integer values. – MrLore Jul 19 '18 at 15:45
  • I'm not smart enough to know what changed in c# recently, but this implementation works much better: `public static bool IsValid(this Enum enumValue)` – UndeadBob Jul 30 '22 at 13:27
16

The canonical answer would be Enum.IsDefined, but that is a: a bit slow if used in a tight loop, and b: not useful for [Flags] enums.

Personally, I'd stop worrying about that, and just switch appropriately, remembering:

  • if it is OK not to recognise everything (and just not do anything), then don't add a default: (or have an empty default: explaining why)
  • if there is a sensible default behaviour, put that in the default:
  • otherwise, handle the ones you know about and throw an exception for the rest:

Like so:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • not familiar with [Flags] enums and performance is not an issue so your answer seems like the reason why enums were invented in the first place ;) looking at your "points" or whatever they're called so you have to have a point there. Bet you didn't get them for nothing, but think about situation of reading config file where there's 257 values in one enum defition. Let alone dozens of other enums. There would be lots of case-rows... – char m Apr 20 '10 at 13:05
  • @matti - that sounds an extreme example; deserialization is a specialist area anyway - most serialization engines offer enum validation for free. – Marc Gravell Apr 20 '10 at 15:07
  • @matti - on a side note; I'd say treat answers based on their individual merits. I sometimes get things completely wrong, and somebody with "rep 17" could just as equally give a **perfect** answer. – Marc Gravell Apr 20 '10 at 15:24
  • 1
    The switch answer is fast, but isn't generic. – Eldritch Conundrum Oct 27 '11 at 15:37
13

Use:

Enum.IsDefined ( typeof ( Enum ), EnumValue );
n535
  • 4,983
  • 4
  • 23
  • 28
7

Use Enum.IsDefined.

driis
  • 161,458
  • 45
  • 265
  • 341
5

In order to deal with [Flags] you can also use this solution from C# Cookbook:

First, add a new ALL value to your enum:

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

Then, check if the value is in ALL:

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}
Mario Levrero
  • 3,345
  • 4
  • 26
  • 57
  • Small caveat if the flag value is zero it will return true: `Console.WriteLine(((Language)0 & Language.All) == (Language)0);` So you would need to handle zero values with Undefined or convert to int and verify the value is greater than zero – Charles Byrne Jul 08 '22 at 16:51
4

As the others said, Enum.IsDefined returns false even if you have a valid combination of bit flags for an enum decorated with the FlagsAttribute.

Sadly, the only way to create a method returning true for valid bit flags is a bit lengthy:

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

You may want to cache the results of GetCustomAttribute in a dictionary:

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

Note that the code above uses the new Enum constraint on T which is only available since C# 7.3. You need to pass an object value in older versions and call GetType() on it.

Ray
  • 7,940
  • 7
  • 58
  • 90
  • At least in .Net 4.8 'Type.CustomAttributes' is available. Use enumType.CustomAttributes instead of querrying them again. – TomB May 14 '21 at 11:13
  • `CustomAttributes` was deemed slower when it was added, and it was added because WinRT did not support `GetCustomAttributes()`. I'm not sure how important that information still is. – Ray May 15 '21 at 09:24
2

One way to do would be to rely on casting and enum to string conversion. When casting int to an Enum type the int is either converted to a corresponding enum value or the resulting enum just contains int as a value if enum value is not defined for the int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

Not tested for any edge cases.

maulik13
  • 3,656
  • 1
  • 25
  • 36
1

I know this is an old question, but I ran into this today, and I wanted to expand on Josh Comley's answer (https://stackoverflow.com/a/23177585/3403999)

There's a couple of wrong assumptions in Josh's answer that I wanted to address:

  1. It assumes that the '-' is always the negative sign. I don't know if there is any cultures that use a different sign, but .Net certainly allows for it in the NumberFormatInfo (https://learn.microsoft.com/en-us/dotnet/api/system.globalization.numberformatinfo.negativesign?view=net-5.0). About the only one I can think of that might be common is the parenthesis, ie (1) == -1.
  2. Enum members have to start with an alphabetic character. Specifically, I know you can use an underscore as the first char. IE, enum MyEnum { _One = 1 } is valid.
  3. Not really sure this exactly wrong, but it made the assumption that anything outside the range of '0' to '9' and '-' is a valid alphabetic character. It seemed like a bad assumption cause there are control characters outside that range that would return true - albeit, I don't think you can get those control characters into an enum member name without it throwing a compile error.

Anyway, here's my updated solution:

public static bool IsValid<TEnum>(this TEnum value) where TEnum : System.Enum
{
    char first = value.ToString()[0];
    return (char.IsLetter(first) || first == '_');
}

I did discover that you can use Unicode letters from other languages in enum member names (https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/identifier-names). My solution still passes in this regard. I tested with the following enum: enum MyEnum { \u05D0 }. The enum compiled, and the IsValid returned true.

I was curious what kind of performance hit you'd take going this route vs using a static helper class with a HashSet that is filled with Enum.GetValues(typeof(TEnum)) where you check to see if the HashSet contains the enum value. The thought being that both Enum.GetValues and Enum.IsDefined are just wrappers around expensive Reflection hits, so you do the Reflection once with GetValues, cache the results, and then just check the HashSet going forward.

I ran a fairly simple test with a StopWatch and Random that would generate valid & invalid enum values, and then I ran them through 3 different methods: the ToString method, the GetValues HashSet method, and the IsDefined method. I had them do each method int.MaxValue times. The results:

  • ToString averaged about 2 minutes every time I ran it 2 billion times.
  • GetValues HashSet about 50 seconds every time I ran it 2 billion times.
  • IsDefined about 5 minutes every time I ran it 2 billion times.

So all the solutions recommending IsDefined are probably a bad idea if performance is a concern, or your doing a loop. If you are only using it somehow validate user input on single instances, it probably doesn't matter.

For the HashSet, it's a small performance hit for each different enum you run through it (cause the first time a new enum type gets ran through generates a new static HashSet). Not scientific, but it seemed my break even point on my PC was about 200k to 300k runs for a single enum before it started out performing using the ToString method.

The ToString method, while not the fastest had the added benefit of handling Flags enums that neither the IsDefined nor HashSet accommodate.

If performance really is a concern, don't use any of these 3 methods. Instead write a method that validates on a specific enum optimized to that enum.

Also note that my tests were with relatively small enums (5 or so elements). I don't know how performance between ToString vs HashSet once you start getting into larger enums.

B.O.B.
  • 704
  • 4
  • 10