我读过一些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。是否有一种方法来验证进入枚举的值?


签出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

使用Enum.IsDefined。


Use:

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

规范的答案是Enum。IsDefined,但这是a:在一个紧密循环中使用有点慢,b:对[Flags]枚举无效。

就我个人而言,我不会再担心这个问题,只要适当地转换一下,记住:

如果不识别所有内容(只是不做任何事情)是可以的,那么就不要添加默认值:(或使用空默认值:解释原因) 如果有一个明智的默认行为,把它放在默认: 否则,处理你知道的那些,并对其余的抛出异常:

像这样:

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

上述解决方案不处理[Flags]情况。

我下面的解决方案可能会有一些性能问题(我相信可以以各种方式进行优化),但本质上它总是证明enum值是否有效。

它依赖于三个假设:

c#中的枚举值只允许是int型,绝对不能是其他类型 c#中的枚举名称必须以字母字符开头 无效的枚举名称不能带有负号:-

在一个enum上调用ToString(),如果没有匹配的enum(是否有标志),则返回int值。如果匹配了一个允许的enum值,它将打印匹配项的名称。

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"

记住这两条规则,我们可以假设如果.NET Framework正确地完成了它的工作,任何对有效enum的ToString()方法的调用都将导致一个以字母字符为第一个字符的东西:

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

有人可以称之为“黑客”,但其优点是,通过依赖微软自己的Enum和c#标准实现,您不必依赖自己可能存在bug的代码或检查。在性能不是特别关键的情况下,这将节省大量讨厌的开关语句或其他检查!

Edit

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

一种方法是依赖于强制转换和枚举到字符串的转换。当将int转换为Enum类型时,int要么被转换为相应的Enum值,要么如果没有为int定义Enum值,则结果Enum仅包含int作为值。

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

没有对任何边缘情况进行测试。


为了处理[Flags],你也可以使用c# Cookbook中的这个解决方案:

首先,添加一个新的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);
    }
}

正如其他人所说,Enum。IsDefined返回false,即使你有一个用FlagsAttribute装饰的enum的有效的位标志组合。

遗憾的是,创建一个为有效位标志返回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;
}

注意,上面的代码在T上使用了新的Enum约束,这是c# 7.3以来才有的。您需要在旧版本中传递一个对象值,并对其调用GetType()。


我知道这是一个老问题,但我今天遇到了这个问题,我想扩展Josh Comley的答案(https://stackoverflow.com/a/23177585/3403999)

Josh的回答中有几个错误的假设,我想指出:

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. 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. 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.

总之,这是我的最新解决方案:

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

我发现可以在枚举成员名中使用来自其他语言的Unicode字母(https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/identifier-names)。在这方面,我的解决办法仍然可行。我使用以下enum进行测试:enum MyEnum {\u05D0}。已编译的枚举,IsValid返回true。

我很好奇,与使用带有枚举值(enumenumvalues (typeof(TEnum))填充的HashSet的静态helper类相比,走这条路会带来什么样的性能打击,其中您可以检查HashSet是否包含enum值。这个想法是两个Enum。GetValues和Enum。IsDefined只是对昂贵的反射命中的包装,所以你用GetValues做一次反射,缓存结果,然后只检查HashSet。

我使用StopWatch和Random运行了一个相当简单的测试,它将生成有效和无效的enum值,然后我通过3个不同的方法运行它们:ToString方法、GetValues HashSet方法和IsDefined方法。每个方法都是int。执行时间。结果:

当我运行20亿次时,ToString平均每次大约2分钟。 GetValues HashSet每次运行20亿次大约50秒。 每次运行20亿次,IsDefined大约5分钟。

因此,如果性能是一个问题,或者您正在进行循环,那么推荐IsDefined的所有解决方案可能都不是一个好主意。如果您只是在单个实例上使用它来验证用户输入,那么这可能无关紧要。

对于HashSet,对于运行它的每个不同枚举,性能都会受到较小的影响(因为第一次运行一个新的枚举类型会生成一个新的静态HashSet)。不科学,但似乎我的PC上的盈亏平衡点是单个枚举运行大约200k到300k次,然后才开始使用ToString方法执行。

ToString方法虽然不是最快的,但它在处理IsDefined和HashSet都不能容纳的Flags枚举方面有额外的好处。

如果性能确实是一个问题,不要使用这3种方法中的任何一种。相反,编写一个方法来验证针对该枚举优化的特定枚举。

还要注意,我的测试使用的是相对较小的枚举(大约5个元素)。我不知道当你开始使用更大的枚举时,ToString和HashSet之间的性能如何。