验证枚举值


73

我需要验证一个整数才能知道是否为有效的枚举值。

用C#做到这一点的最佳方法是什么?


1
对于带有标志的方法,在重复的问题上签出此答案可能是有用的:stackoverflow.com/a/23177585/5190842
Erik 2015年

检查EnumDataTypeAttribute
永恒的

最快的方法是不验证枚举并使用默认行为,任何枚举都应具有“ None”或“ Default”之类的成员。您可以使用切换默认大小写。
M.kazem Akhgary

Answers:


88

您一定会喜欢这些人,他们认为数据不仅总是来自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')


13
另请注意,Enum.IsDefined使用的反射可能会导致性能问题
Guldan

1
在已经检查IsDefined()为什么不投了int类型的成功条件:retVal = (TEnum)enumValue
马特·詹金斯

1
@jeromej我在问为什么我们不能使用retVal = (TEnum)enumvalue而不是retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue)成功条件。我们已经确认了这一点enumValue IsDefined()
马特·詹金斯

@MattJenkins对。我看不懂,我的坏。现在删除我的评论。
jeromej

26

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

为什么

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

哪里

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

就是说,我花了几天时间研究和测试许多方法,包括对我可以找到或设计的每种方法的性能进行性能分析。

在System.Enum中进行任何调用都很慢,以至于包含数百或数千个对象的函数的性能明显下降,这些对象的属性中具有一个或多个枚举,并且必须针对边界进行验证。

最重要的是,在验证枚举值时,请远离System.Enum类中的所有内容,这实在太慢了。

结果

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

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

另外请注意,如果在常规int32上为“ <”>”(在我的测试中获得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个滴答= 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个滴答=获胜者快42倍和316倍。

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

-eol-


10
我们的大量枚举不是连续的,因此在许多情况下不是一种选择。在测试场景中,“ Enum.IsDefined(typeof(T)”会很慢,因为.net会进行大量反射,装箱等操作。每次在导入文件中解析一行时调用此方法都不会很快。性能是关键,那么我将在导入开始时调用一次Enum.GetValues,通过简单的<>比较将永远不会如此之快,但是您知道它将对所有枚举都适用。或者,您可以拥有更智能的枚举解析器,我将发布另一个答案,因为没有足够的空间进行整齐的回答!
Vman 2014年

1
@johnny 5-根据以上信息:该方法仅在枚举是“自动”样式时才有效,其中每个枚举元素是一个递增的整数值,例如0、1、2、3、4,...。不能与具有非增量值的标志或枚举一起正常使用。
deegee 2015年

@Vman-“每次在解析导入文件中的一行时调用它都不会很快。” -它将大大快于驱动器的读取速度。
deegee 2015年

也许其他一些过程可能需要一部分硬盘操作?死亡减少一千。
Vman 2015年

过早的优化。绝大多数情况下,文件中的枚举数量不足以对性能产生任何重大影响。对它进行测试,如果存在性能问题,则进行测试并选择最佳方法来处理它。
deegee

14

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

对此答案的另一种解释是HashSet<MyEnum>int如果您要验证enum而不是,则可以使用,从而无需强制转换为int。进行设置的代码如下:var validVals = new HashSet<MyEnum>(Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>());。用法当然是相同的。
Erik

碰巧的是,由于某种原因,HashSet <Int>比HashSet <enum>快得多,因此在执行检查之前将其强制转换为int可以大大加快处理速度。
Dwedit

9

布拉德•艾布拉姆斯(Brad Abrams)Enum.IsDefined在他的文章“过分简化的危险”中特别警告道。

摆脱此要求(即验证枚举的需要)的最佳方法是删除用户可能会误解的方法,例如某种输入框。例如,将枚举与下拉列表配合使用,以仅强制执行有效的枚举。


22
仅将枚举放入下拉框的建议对于WinForms效果很好,但对于WebForms则不起作用,在WebForms中,您需要针对恶意输入进行验证。
布拉德·威尔逊

6
我的输入来自一个XML文件,该文件是由许多可能的程序之一生成的,我无法控制数据质量在哪里变化很大。Enum.IsDefined是否真的那么糟糕,因为在这种情况下对我来说似乎最好?
理查德·加塞德

@Richard与生活中的任何事物一样,在某些特定情况下,通常的坏主意将是解决该问题的适当方法。如果您认为这是适合您的情况的最佳解决方案,请继续。:)即使在某些情况下,单例和全局变量以及嵌套if也是一个好主意……
Jon Limjap 2011年

这就是为什么您总是在switch语句中定义默认大小写的原因。
2012年

7
您的方法(限制UI中可用的功能)具有以下缺点Enum.IsDefined:如果UI代码与枚举不同步,则您可能会获得超出范围的值(即未在枚举中声明)。尽管仍然需要处理这些值(例如defaultswitch语句中使用case ),但是Enum.IsDefined在将值存储在对象的字段中之前使用它似乎是一种好习惯:它允许错误被尽早发现,而不是让错误一直浮现直到错误发生。现在还不清楚伪造的价值来自何处。
苏珊·杜彭(SuzanneDupéron)2013年

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类可以更有效地编写(这只是一个快速的演示),但坦率地说,谁在乎导入循环之外发生的事情?唯一需要超快速的位是在循环内。这就是采取抽象类路线的原因,以避免循环中不必要的if-enumContiguous-then-else(工厂Create本质上是在先进行此操作)。您会注意到一些虚伪,为简洁起见,此代码将功能限制为int枚举。我应该使用IConvertible而不是直接使用int,但是这个答案已经足够罗word了!


对于较大的枚举,可以使用字典在O(1)时间中查找枚举值。
M.kazem Akhgary

6

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

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


2

这是基于在线多个帖子的方式。这样做的原因是确保标记有Flagsattribute的枚举也可以成功验证。

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);
    }
}

1

基于Timo的答案,我精心设计了以下扩展方法(C#6语法)以提供快速,通用的解决方案。

这样可以避免的性能问题Enum.IsDefined,并且语法更加简洁。

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 readonly HashSet<T> DefinedValues = new HashSet<T>((T[])Enum.GetValues(typeof(T)));
    }
}

用法:

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

1

您可以将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");
  }
}

0

我发现此链接可以很好地回答它。它用:

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

如果将非枚举整数传递给它,这不会抛出异常吗?
理查德·加赛德

3
@Richard -是的,它会... 3年,因为我写了这个天上帮助我,如果我知道我是不是想着其他-如果它抛出它不是
麦克POLEN

抛出异常也会导致性能损失。如果有更好的解决方案,则永远不要抛出异常。
Kody

0

要验证值在枚举中是否为有效值,只需要调用静态方法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
}

3
并不是因为错,而是因为迟了8年才写了一个答案,而没有提供公认的答案中没有的新信息,所以对此表示不满。
Ben Voigt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.