验证枚举值

时间:2008-08-17 13:14:43

标签: c# validation enums

我需要验证一个整数,以了解它是否是一个有效的枚举值。

在C#中执行此操作的最佳方法是什么?

12 个答案:

答案 0 :(得分:75)

你必须爱这些人,他们认为数据不仅总是来自用户界面,而且还有你控制范围内的用户界面!

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')

答案 1 :(得分:20)

恕我直言,标记为答案的帖子不正确 参数和数据验证是几十年前钻进我的事情之一。

<强> WHY

验证是必需的,因为基本上任何整数值都可以分配给枚举而不会引发错误 我花了很多天研究C#枚举验证,因为在许多情况下它是必要的功能。

<强> WHERE

我的枚举验证的主要目的是验证从文件读取的数据:您永远不知道该文件是否已损坏,或是在外部修改,还是故意被黑客攻击。
通过从剪贴板粘贴的应用程序数据的枚举验证:您永远不知道用户是否编辑了剪贴板内容。

尽管如此,我花了几天时间研究和测试了许多方法,包括分析我能找到或设计的每种方法的性能。

对System.Enum中的任何内容进行调用是如此之慢,以至于对包含数百或数千个对象的函数的性能明显下降,这些对象的属性中有一个或多个枚举,必须对边界进行验证。

底线,在验证枚举值时远离System.Enum类中的所有,它非常慢。

<强> RESULT

我目前用于枚举验证的方法可能会引起许多程序员的注意,但对于我的特定应用程序设计来说,它是最不邪恶的。

我定义了一个或两个常量,它们是枚举的上限和(可选)下限,并在一对if()语句中使用它们进行验证。
一个缺点是,如果更改枚举,必须确保更新常量 此方法也只有在枚举是“自动”样式时才有效,其中每个枚举元素都是一个增量整数值,如0,1,2,3,4,....它将无法正常使用Flags或enums具有非增量值。

另请注意,如果“&lt;”,此方法几乎与常规方法一样快“&gt;” 中在常规的int32s上(我的测试得分为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;
}

<强>性能

对于那些感兴趣的人,我在枚举验证中描述了以下变体,以下是结果。

使用随机整数输入值在每个方法的循环中以100万次循环执行分析。每次测试运行10次以上并取平均值。刻度结果包括执行的总时间,其中包括随机数生成等,但这些将在测试中保持不变。 1滴= 10ns。

请注意,此处的代码不是完整的测试代码,它只是基本的枚举验证方法。测试的这些还有很多其他的变化,所有这些变化的结果都类似于这里显示的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;
}

Enum.IsDefined = 1,800,000个刻度
注意:此代码版本不会限制为Min / Max,但如果超出范围则返回Default。

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 ticks =赢家42x,快316倍。

public static MyEnum Clamp(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

-eol -

答案 2 :(得分:10)

正如其他人所提到的那样,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
}

答案 3 :(得分:9)

Brad Abrams在帖子The Danger of Oversimplification中特别警告 Enum.IsDefined

摆脱此要求的最佳方法(即,需要验证枚举)是删除用户可能出错的方法,例如某种输入框。例如,使用包含下拉菜单的枚举来强制执行有效的枚举。

答案 4 :(得分:6)

这个答案是为了回应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类可以更有效地编写(这只是一个快速的黑客演示),但坦率地说谁关心在import循环之外发生了什么?唯一需要超快的位是在循环内。这就是采用抽象类路由的原因,以避免在循环中出现不必要的if-enumContiguous-then-else(工厂Create基本上是这样做的)。 你会注意到一些虚伪,为简洁起见,这段代码限制了int-enums的功能。我应该使用IConvertible而不是直接使用int,但这个答案已经足够冗长了!

答案 5 :(得分:2)

这是基于多个在线帖子的方式。这样做的原因是为了确保标记有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);
    }
}

答案 6 :(得分:1)

我发现这个link很好地回答了它。它使用:

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)

答案 7 :(得分:0)

我选择了该解决方案,将性能,便利性和可维护性融为一体。

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)

答案 8 :(得分:0)

这是一个快速的通用解决方案,它使用静态构造的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)));
        }
    }
}

请注意,通过使用带有字符串键的字典(注意大小写不敏感和数字字符串表示形式),该方法也很容易扩展到枚举解析。

答案 9 :(得分:0)

在Timo的答案的基础上,我将其转换为具有其他一些小的调整的扩展方法(C#6语法)。

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// </summary>
    public static bool IsDefined<T>(this T enumValue)
        where T : Enum
        => EnumValueCache<T>.DefinedValues.Contains(enumValue);

    /// <summary>
    /// Caches the defined values for each enum type for which this class is accessed.
    /// </summary>
    private static class EnumValueCache<T>
        where T : Enum
    {
        public static HashSet<T> DefinedValues { get; } = new HashSet<T>((T[])Enum.GetValues(typeof(T)));
    }
}

用法:

if (myEnumValue.IsDefined())
   // ...

答案 10 :(得分:0)

您可以将FluentValidation用于您的项目。这是“枚举验证”的简单示例

让我们使用FluentValidation创建一个EnumValidator类;

public class EnumValidator<TEnum> : AbstractValidator<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable
{
    public EnumValidator(string message)
    {
        RuleFor(a => a).Must(a => typeof(TEnum).IsEnum).IsInEnum().WithMessage(message);
    }

}

现在,我们创建了我们的enumvalidator类;让我们创建一个调用enumvalidor类的类;

 public class Customer 
{
  public string Name { get; set; }
  public Address address{ get; set; }
  public AddressType type {get; set;}
}
public class Address 
{
  public string Line1 { get; set; }
  public string Line2 { get; set; }
  public string Town { get; set; }
  public string County { get; set; }
  public string Postcode { get; set; }

}

public enum AddressType
{
   HOME,
   WORK
}

是时候为客户类中的地址类型调用我们的枚举验证器了。

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
   {
     RuleFor(x => x.type).SetValidator(new EnumValidator<AddressType>("errormessage");
  }
}

答案 11 :(得分:-1)

要验证值是否是枚举中的有效值,您只需要调用静态方法Enum.IsDefined

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
}