[英]Is there a way to check if int is legal enum in C#?
我已經閱讀了一些 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
。 有沒有辦法驗證進入枚舉的值?
用法:
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
上述解決方案不處理[Flags]
情況。
我下面的解決方案可能有一些性能問題(我相信可以通過各種方式進行優化),但本質上它總是會證明枚舉值是否有效。
它依賴於三個假設:
int
,絕對沒有別的-
如果沒有匹配的枚舉(標志或不匹配ToString()
,則在枚舉上調用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 != '-';
}
可以將其稱為“hack”,但優點是通過依賴 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());
}
}
規范的答案是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");
}
用:
Enum.IsDefined ( typeof ( Enum ), EnumValue );
使用Enum.IsDefined 。
為了處理[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);
}
}
一種方法是依靠強制轉換和枚舉到字符串的轉換。 將 int 轉換為 Enum 類型時,int 將轉換為相應的枚舉值,或者如果未為 int 定義枚舉值,則生成的枚舉僅包含 int 作為值。
enum NetworkStatus{
Unknown=0,
Active,
Slow
}
int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();
未針對任何邊緣情況進行測試。
正如其他人所說, Enum.IsDefined
返回false
即使您有使用FlagsAttribute
修飾的枚舉的位標志的有效組合。
可悲的是,創建一個為有效位標志返回 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 起可用。 您需要在舊版本中傳遞一個object value
並對其調用GetType()
。
我知道這是一個老問題,但我今天遇到了這個問題,我想擴展 Josh Comley 的回答( https://stackoverflow.com/a/23177585/3403999 )
我想解決喬希的回答中有幾個錯誤的假設:
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 == '_');
}
我確實發現您可以在枚舉成員名稱中使用來自其他語言的 Unicode 字母( https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/identifier-names )。 我的解決方案在這方面仍然通過。 我使用以下枚舉進行了測試: enum MyEnum { \א }
。 枚舉已編譯,並且 IsValid 返回 true。
我很好奇走這條路與使用帶有 HashSet 的靜態幫助器類相比,您會采取什么樣的性能打擊,該類中填充了Enum.GetValues(typeof(TEnum))
,您可以在其中檢查 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 之間的性能如何。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.