题
我需要验证一个整数以了解它是否是有效的枚举值。
在 C# 中执行此操作的最佳方法是什么?
解决方案
您必须喜欢这些人,他们认为数据不仅总是来自 UI,而且还来自您可以控制的 UI!
IsDefined
对于大多数情况来说都很好,你可以从以下开始:
public static bool TryParseEnum<TEnum>(this int enumValue, out TEnum retVal)
{
retVal = default(TEnum);
bool success = Enum.IsDefined(typeof(TEnum), enumValue);
if (success)
{
retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue);
}
return success;
}
(显然,如果您认为它不是合适的 int 扩展,则可以删除“this”)
其他提示
恕我直言,标记为答案的帖子不正确。
参数和数据验证是几十年前我就被灌输的内容之一。
为什么
需要验证,因为基本上任何整数值都可以分配给枚举而不会引发错误。
我花了很多天研究 C# 枚举验证,因为它在很多情况下是必要的功能。
在哪里
对我来说,枚举验证的主要目的是验证从文件中读取的数据:您永远不知道文件是否已损坏,或被外部修改,或被故意黑客攻击。
并对从剪贴板粘贴的应用程序数据进行枚举验证:您永远不知道用户是否编辑了剪贴板内容。
也就是说,我花了几天时间研究和测试许多方法,包括分析我能找到或设计的每种方法的性能。
调用 System.Enum 中的任何内容都非常慢,对于包含数百或数千个对象的函数来说,这是一个明显的性能损失,这些对象的属性中有一个或多个枚举,必须验证边界。
底线是,远离 一切 在 System.Enum 类中验证枚举值时,速度非常慢。
结果
我目前用于枚举验证的方法可能会引起许多程序员的白眼,但恕我直言,对于我的特定应用程序设计来说,这是最不邪恶的。
我定义一个或两个常量,它们是枚举的上限和(可选)下限,并在一对 if() 语句中使用它们进行验证。
一个缺点是,如果更改枚举,则必须确保更新常量。
此方法也仅在枚举为“自动”样式时有效,其中每个枚举元素都是增量整数值,例如 0,1,2,3,4,....它无法与具有非增量值的标志或枚举正常工作。
另请注意,此方法几乎与常规 int32 上的 if "<" ">" 一样快(在我的测试中得分为 38,000 个刻度)。
例如:
public const MyEnum MYENUM_MINIMUM = MyEnum.One;
public const MyEnum MYENUM_MAXIMUM = MyEnum.Four;
public enum MyEnum
{
One,
Two,
Three,
Four
};
public static MyEnum Validate(MyEnum value)
{
if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
return value;
}
表现
对于那些感兴趣的人,我介绍了枚举验证的以下变体,以下是结果。
使用随机整数输入值对每种方法进行一百万次循环,在发布编译时执行分析。每个测试运行 10 次以上并取平均值。刻度结果包括执行的总时间,其中包括随机数生成等。但这些在整个测试中将保持不变。1 刻 = 10 纳秒。
请注意,这里的代码不是完整的测试代码,它只是基本的枚举验证方法。这些测试还存在许多其他变化,所有这些变化的结果都与此处所示的 1,800,000 个蜱虫测试的结果类似。
列出从最慢到最快的四舍五入结果,希望没有拼写错误。
方法中确定的界限 = 13,600,000 个刻度
public static T Clamp<T>(T value)
{
int minimum = Enum.GetValues(typeof(T)).GetLowerBound(0);
int maximum = Enum.GetValues(typeof(T)).GetUpperBound(0);
if (Convert.ToInt32(value) < minimum) { return (T)Enum.ToObject(typeof(T), minimum); }
if (Convert.ToInt32(value) > maximum) { return (T)Enum.ToObject(typeof(T), maximum); }
return value;
}
枚举已定义 = 1,800,000 个刻度
笔记:此代码版本不会限制最小/最大,但如果超出范围则返回默认值。
public static T ValidateItem<T>(T eEnumItem)
{
if (Enum.IsDefined(typeof(T), eEnumItem) == true)
return eEnumItem;
else
return default(T);
}
System.Enum 通过强制转换转换 Int32 = 1,800,000 个刻度
public static Enum Clamp(this Enum value, Enum minimum, Enum maximum)
{
if (Convert.ToInt32(value) < Convert.ToInt32(minimum)) { return minimum; }
if (Convert.ToInt32(value) > Convert.ToInt32(maximum)) { return maximum; }
return value;
}
if() 最小/最大常量 = 43,000 个刻度 = 获胜者快 42 倍和 316 倍。
public static MyEnum Clamp(MyEnum value)
{
if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
return value;
}
-停产-
正如其他人提到的, Enum.IsDefined
速度很慢,如果它在循环中,您必须注意这一点。
进行多重比较时,更快的方法是首先将值放入 HashSet
. 。然后只需使用 Contains
检查该值是否有效,如下所示:
int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet<int> validVals = new HashSet<int>((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
// is valid
}
布拉德·艾布拉姆斯特别警告 Enum.IsDefined
在他的帖子中 过于简单化的危险.
摆脱此要求(即验证枚举的需要)的最佳方法是删除用户可能出错的方式,例如某种输入框。例如,将枚举与下拉菜单一起使用,以强制仅使用有效的枚举。
这个答案是对 deegee 的答案的回应,该答案提出了 System.Enum 的性能问题,因此不应将其视为我首选的通用答案,更多地解决严格性能场景中的枚举验证。
如果您遇到关键任务性能问题,其中缓慢但功能性的代码在紧密循环中运行,那么我个人会考虑在可能的情况下将该代码移出循环,而不是通过减少功能来解决。例如,如果将来有人决定弃用某些枚举值,则将代码限制为仅支持连续枚举可能会是查找错误的噩梦。简单地说,您可以在开始时调用 Enum.GetValues 一次,以避免触发所有反射等数千次。这应该会给你带来立竿见影的性能提升。如果您需要更多性能并且知道许多枚举是连续的(但您仍然想支持“gappy”枚举),您可以更进一步并执行以下操作:
public abstract class EnumValidator<TEnum> where TEnum : struct, IConvertible
{
protected static bool IsContiguous
{
get
{
int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();
int lowest = enumVals.OrderBy(i => i).First();
int highest = enumVals.OrderByDescending(i => i).First();
return !Enumerable.Range(lowest, highest).Except(enumVals).Any();
}
}
public static EnumValidator<TEnum> Create()
{
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("Please use an enum!");
}
return IsContiguous ? (EnumValidator<TEnum>)new ContiguousEnumValidator<TEnum>() : new JumbledEnumValidator<TEnum>();
}
public abstract bool IsValid(int value);
}
public class JumbledEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
private readonly int[] _values;
public JumbledEnumValidator()
{
_values = Enum.GetValues(typeof (TEnum)).Cast<int>().ToArray();
}
public override bool IsValid(int value)
{
return _values.Contains(value);
}
}
public class ContiguousEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
private readonly int _highest;
private readonly int _lowest;
public ContiguousEnumValidator()
{
List<int> enumVals = Enum.GetValues(typeof (TEnum)).Cast<int>().ToList();
_lowest = enumVals.OrderBy(i => i).First();
_highest = enumVals.OrderByDescending(i => i).First();
}
public override bool IsValid(int value)
{
return value >= _lowest && value <= _highest;
}
}
你的循环变成这样:
//Pre import-loop
EnumValidator< MyEnum > enumValidator = EnumValidator< MyEnum >.Create();
while(import) //Tight RT loop.
{
bool isValid = enumValidator.IsValid(theValue);
}
我确信 EnumValidator 类可以编写得更有效(这只是一个快速演示的技巧),但坦率地说,谁关心导入循环之外发生了什么?唯一需要超快的部分是在循环内。这就是采用抽象类路线的原因,以避免循环中不必要的 if-enumContigously-then-else (工厂 Create 本质上是预先执行此操作)。您会注意到有点虚伪,为了简洁起见,此代码将功能限制为 int-enum。我应该使用 IConvertible 而不是直接使用 int,但这个答案已经足够啰嗦了!
这就是我根据网上多篇帖子的做法。这样做的原因是为了确保枚举标记为 Flags
属性也可以成功验证。
public static TEnum ParseEnum<TEnum>(string valueString, string parameterName = null)
{
var parsed = (TEnum)Enum.Parse(typeof(TEnum), valueString, true);
decimal d;
if (!decimal.TryParse(parsed.ToString(), out d))
{
return parsed;
}
if (!string.IsNullOrEmpty(parameterName))
{
throw new ArgumentException(string.Format("Bad parameter value. Name: {0}, value: {1}", parameterName, valueString), parameterName);
}
else
{
throw new ArgumentException("Bad value. Value: " + valueString);
}
}
我找到了这个 关联 这很好地回答了这个问题。它用:
(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)
我选择了这个兼具性能、便利性和可维护性的解决方案。
public enum DogBreed
{
Unknown = 0,
Beagle = 1,
Labrador = 2,
PeruvianIncaOrchid = 3,
}
public static class DogBreedExtensions
{
public static bool IsValidDogBreed(this DogBreed breed)
{
var v = (int)breed;
return v >= 1 && v <= 3;
}
}
并这样使用它:
var goodInput = 2;
var goodDog = (DogBreed)goodInput;
goodDog.IsValidDogBreed(); // true.
var badInput = 7;
var badDog = (DogBreed)badInput; // no problem, bad data here we come.
badDog.IsValidDogBreed(); // false, you're in the doghouse!
就我而言,调用 Enum.IsDefined 只会让我半途而废,因为我通常想拒绝“未知”枚举值。
这种方法避免了 Enum.IsDefined 的性能问题,并定义了接近枚举的验证规则,这是我喜欢的。
扩展方法很容易适应不断变化的需求,并且如果需要的话可以应用于 int - (即 public static bool IsValidDogBreed(this int breed)
)
这是一个快速通用的解决方案,使用静态构造的 HashSet<T>
.
您可以在工具箱中定义一次,然后将其用于所有枚举验证。
public static class EnumHelpers
{
/// <summary>
/// Returns whether the given enum value is a defined value for its type.
/// Throws if the type parameter is not an enum type.
/// </summary>
public static bool IsDefined<T>(T enumValue)
{
if (typeof(T).BaseType != typeof(System.Enum)) throw new ArgumentException($"{nameof(T)} must be an enum type.");
return EnumValueCache<T>.DefinedValues.Contains(enumValue);
}
/// <summary>
/// Statically caches each defined value for each enum type for which this class is accessed.
/// Uses the fact that static things exist separately for each distinct type parameter.
/// </summary>
internal static class EnumValueCache<T>
{
public static HashSet<T> DefinedValues { get; }
static EnumValueCache()
{
if (typeof(T).BaseType != typeof(System.Enum)) throw new Exception($"{nameof(T)} must be an enum type.");
DefinedValues = new HashSet<T>((T[])System.Enum.GetValues(typeof(T)));
}
}
}
请注意,通过使用带有字符串键的字典(注意不区分大小写和数字字符串表示),这种方法也可以轻松扩展到枚举解析。
要验证某个值是否是枚举中的有效值,只需调用静态方法 枚举已定义.
int value = 99;//Your int value
if (Enum.IsDefined(typeof(your_enum_type), value))
{
//Todo when value is valid
}else{
//Todo when value is not valid
}