我已经阅读了一些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
。有没有办法验证进入枚举的值?
答案 0 :(得分:211)
用法:
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
答案 1 :(得分:27)
以上解决方案不涉及[Flags]
情况。
我的解决方案可能存在一些性能问题(我确定可以通过各种方式进行优化),但基本上它将始终证明枚举值是否有效。
它依赖于三个假设:
int
,绝对没有其他内容-
如果没有枚举(标志或不匹配),则在枚举上调用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"
考虑到这两个规则,我们可以假设,如果.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 != '-';
}
有人可以称之为&#34; hack&#34;,但优点是依靠微软自己实施的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());
}
}
答案 2 :(得分:14)
规范的答案是Enum.IsDefined
,但这是:如果在紧密循环中使用会有点慢,而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");
}
答案 3 :(得分:6)
使用:
Enum.IsDefined ( typeof ( Enum ), EnumValue );
答案 4 :(得分:5)
答案 5 :(得分:4)
为了处理ALL
,您还可以使用this solution from C# Cookbook:
首先,为您的枚举添加新的[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);
}
}
:
each
答案 6 :(得分:2)
一种方法是依靠cast和enum来进行字符串转换。当将int转换为Enum类型时,int被转换为相应的枚举值,或者如果没有为int定义枚举值,则生成的枚举仅包含int作为值。
enum NetworkStatus{
Unknown=0,
Active,
Slow
}
int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();
未针对任何边缘情况进行测试。
答案 7 :(得分:0)
正如其他人所说,Enum.IsDefined
会返回false
,即使您有一个用FlagsAttribute
装饰的枚举的位标志的有效组合。
遗憾的是,创建为 valid 位标志返回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;
}
请注意,上面的代码对Enum
使用了新的T
约束,该约束仅从C#7.3起可用。您需要通过较旧版本的object value
并在其上调用GetType()
。
答案 8 :(得分:0)
我知道这是一个老问题,但我今天遇到了这个问题,我想扩展 Josh Comley 的回答 (https://stackoverflow.com/a/23177585/3403999)
Josh 的回答中有几个错误的假设需要解决:
enum MyEnum { _One = 1 }
有效。无论如何,这是我更新的解决方案:
public static bool IsValid<TEnum>(this TEnum value) where TEnum : System.Enum
{
char first = value.ToString()[0];
return (char.IsLetter(first) || first == '_');
}
我确实发现您可以在枚举成员名称 (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/identifier-names) 中使用来自其他语言的 Unicode 字母。我的解决方案在这方面仍然通过。我使用以下枚举进行了测试:enum MyEnum { \u05D0 }
。枚举已编译,并且 IsValid 返回 true。
我很好奇走这条路与使用带有填充了 Enum.GetValues(typeof(TEnum))
的 HashSet 的静态帮助器类相比,您会采取什么样的性能影响,您可以在其中检查 HashSet 是否包含枚举值。想法是 Enum.GetValues 和 Enum.IsDefined 都只是昂贵的反射命中的包装器,因此您可以使用 GetValues 进行一次反射,缓存结果,然后继续检查 HashSet。
我使用 StopWatch 和 Random 运行了一个相当简单的测试,可以生成有效和无效的枚举值,然后我通过 3 种不同的方法运行它们:ToString 方法、GetValues HashSet 方法和 IsDefined 方法。我让他们做每个方法 int.MaxValue 次。结果:
因此,如果性能是一个问题,或者您正在执行循环,那么所有推荐 IsDefined 的解决方案都可能是一个坏主意。如果您只是使用它以某种方式验证单个实例上的用户输入,则可能无关紧要。
对于 HashSet,对于您运行的每个不同的枚举,它的性能都会受到很小的影响(因为第一次运行新的枚举类型会生成一个新的静态 HashSet)。不科学,但在开始使用 ToString 方法执行之前,我的 PC 上的盈亏平衡点似乎是单个枚举的大约 200k 到 300k 运行。
ToString 方法虽然不是最快的方法,但具有处理 IsDefined 和 HashSet 都无法容纳的标志枚举的额外好处。
如果确实需要考虑性能,请不要使用这 3 种方法中的任何一种。而是编写一个方法来验证针对该枚举优化的特定枚举。
另请注意,我的测试使用相对较小的枚举(5 个左右的元素)。一旦您开始使用更大的枚举,我不知道 ToString 与 HashSet 之间的性能如何。