当前位置:  开发笔记 > 编程语言 > 正文

验证枚举值

如何解决《验证枚举值》经验,为你挑选了5个好方法。

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

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



1> Vman..:

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

IsDefined 适用于大多数情况,你可以从:

public static bool TryParseEnum(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')


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

2> deegee..:

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

为什么

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

哪里

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

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

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

最重要的是,在验证枚举值时,远离System.Enum类中的所有内容,它非常慢.

结果

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

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

另请注意,如果常规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 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 ticks
注意:此代码版本不会限制为Min/Max,但如果超出范围则返回Default.

public static T ValidateItem(T eEnumItem)
{
    if (Enum.IsDefined(typeof(T), eEnumItem) == true)
        return eEnumItem;
    else
        return default(T);
}

System.Enum使用强制转换 = 1,800,000刻度转换Int32

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()Min/Max Constants = 43,000 ticks =获胜者42x和316x更快.

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

-eol-


我们的大量枚举不是连续的,因此在许多情况下都不是一个选项.'Enum.IsDefined(typeof(T)'在您的测试场景中会很慢,因为.net正在进行大量的反射,装箱等.每次在导入文件中解析一行时调用它都永远不会很快.如果性能是关键,然后我会看一下在导入开始时调用Enum.GetValues.它将永远不会像一个简单的<>比较快但你知道它适用于所有枚举.或者你可以有一个更智能的枚举解析器,我会发布另一个答案,因为没有空间可以整齐地回应!

3> Doug S..:

正如其他人所提到的那样,Enum.IsDefined很慢,如果它处于循环中,你必须要注意的事情.

在进行多重比较时,更快的方法是首先将值放入a HashSet.然后只需Contains用来检查值是否有效,如下所示:

int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet validVals = new HashSet((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
    // is valid
}



4> Jon Limjap..:

布拉德艾布拉姆斯Enum.IsDefined在他的文章"过度简化的危险"中特别警告.

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


仅将枚举放入下拉框的建议适用于WinForms,但对于需要针对恶意输入进行验证的WebForms失败.
您的方法(限制UI中可用的内容)与"Enum.IsDefined"具有相同的缺点:如果UI代码与枚举不同步,则可以获得超出范围的值(即未在枚举中声明) ).虽然仍然需要处理这些值(比如在`switch`语句中有一个`default` case),但在将一个值存储到对象的字段之前使用`Enum.IsDefined`似乎是一个好习惯:它允许错误尽早抓住,而不是让它漂浮,直到不再清楚伪造价值来自哪里.
我的输入来自一个XML文件,该文件是由许多可能的程序之一产生的,我无法控制数据质量变化很大的程序.Enum.IsDefined真的那么糟糕,因为在这种情况下它对我来说似乎最好吗?

5> Vman..:

这个答案是对deegee的答案的回应,它提出了System.Enum的性能问题,所以不应该把它作为我的首选通用答案,更多地解决紧密性能场景中的枚举验证问题.

如果你有一个任务关键性能问题,在一个紧凑的循环中运行缓慢但功能性的代码,那么我个人会考虑在可能的情况下将该代码移出循环,而不是通过减少功能来解决.例如,如果将来有人决定弃用某些枚举值,那么将代码限制为仅支持连续枚举可能是发现错误的噩梦.简单地说,您可以在开始时调用Enum.GetValues一次,以避免触发所有反射等数千次.这应该可以立即提升性能.如果你需要更多的表现并且你知道很多你的枚举是连续的(但你仍然想要支持'gappy'枚举)你可以更进一步,做一些事情:

public abstract class EnumValidator where TEnum : struct, IConvertible
{
    protected static bool IsContiguous
    {
        get
        {
            int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast().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 Create()
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("Please use an enum!");
        }

        return IsContiguous ? (EnumValidator)new ContiguousEnumValidator() : new JumbledEnumValidator();
    }

    public abstract bool IsValid(int value);
}

public class JumbledEnumValidator : EnumValidator where TEnum : struct, IConvertible
{
    private readonly int[] _values;

    public JumbledEnumValidator()
    {
        _values = Enum.GetValues(typeof (TEnum)).Cast().ToArray();
    }

    public override bool IsValid(int value)
    {
        return _values.Contains(value);
    }
}

public class ContiguousEnumValidator : EnumValidator where TEnum : struct, IConvertible
{
    private readonly int _highest;
    private readonly int _lowest;

    public ContiguousEnumValidator()
    {
        List enumVals = Enum.GetValues(typeof (TEnum)).Cast().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-enums的功能.我应该使用IConvertible而不是直接使用int,但这个答案已经足够冗长!

推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有