兰斯·亨特';s C#编码标准-枚举混淆

兰斯·亨特';s C#编码标准-枚举混淆,c#,.net,coding-style,C#,.net,Coding Style,我的团队最近开始使用文档作为整合编码标准的起点 有一件事我们就是不明白,这里有人能解释一下吗 项目编号为77: 始终验证枚举 之前的变量或参数值 消费它。它们可能包含任何 值,该值指定基础枚举类型 (默认int)支持 例如: 未检查枚举: enum Foo { A= 1, B = 2, C = 3} Foo x = (Foo) 27; // works fine 现在您有了一个未定义的Foo。 他只是说“检查你的输入”。请注意,Enum.IsDefined相对较慢(基于反射)。就个人而言,我倾

我的团队最近开始使用文档作为整合编码标准的起点

有一件事我们就是不明白,这里有人能解释一下吗

项目编号为77:

始终验证枚举 之前的变量或参数值 消费它。它们可能包含任何 值,该值指定基础枚举类型 (默认int)支持

例如:

未检查枚举:

enum Foo { A= 1, B = 2, C = 3}
Foo x = (Foo) 27; // works fine
现在您有了一个未定义的
Foo
。 他只是说“检查你的输入”。请注意,
Enum.IsDefined
相对较慢(基于反射)。就个人而言,我倾向于使用带有
默认值的开关,该开关会引发异常:

switch(x) {
    case Foo.A: /* do something */ break;
    case Foo.B: /* do something */ break;
    case Foo.C: /* do something */ break;
    default: throw new ArgumentOutOfRangeException("x");
}

这是因为这样称呼“测试”方法是完全合法的:

Test((BookCategory)-999);

这会将-999强制转换为BookCategory,但结果(作为“cat”参数传递以进行测试)当然不是BookCategory枚举的有效值。

关键是,您可能希望通过拥有BookCategory类型的参数,您始终拥有一个有意义的图书类别。事实并非如此。我可以打电话:

BookCategory weirdCategory = (BookCategory) 123456;
Test(weirdCategory);
如果枚举表示一组已知的值,那么代码不应该明智地处理该已知集之外的值。测试首先检查参数是否合适

不过,我个人会把逻辑颠倒过来:

public void Test(BookCategory cat)
{
    if (!Enum.IsDefined(typeof(BookCategory), cat))
    {
        throw new ArgumentOutOfRangeException("cat");
    }
}
在C#3中,这可以通过扩展方法轻松完成:

// Can't constrain T to be an enum, unfortunately. This will have to do :)
public static void ThrowIfNotDefined<T>(this T value, string name) where T : struct
{
    if (!Enum.IsDefined(typeof(T), value))
    {
        throw new ArgumentOutOfRangeException(name);
    }
}

如果希望在库的未来版本中使用新值扩展枚举,则不应使用构造

如果(枚举已定义(类型(图书类别),类别))

因为这仅根据枚举定义测试类型是否有效。另一方面,您的代码仅针对当前允许的值进行了测试,并且可能会针对新值失败


有关更多背景信息,请参阅。

我认为上面的评论基本上回答了这个问题。本质上,当我编写这个规则时,我试图传达一种验证所有输入的防御性编码实践。枚举是一种特殊情况,因为许多开发人员错误地认为它们是经过验证的,而事实并非如此。因此,您经常会看到语句或switch语句对于未定义的枚举值是否失败

请记住,默认情况下,枚举只不过是INT的包装器,并像验证INT一样验证它


有关正确使用枚举的更详细讨论,您可以查看Brad Abrams和Krzysztof Cwalina的博客文章或他们的优秀著作《框架设计指南:可重用.NET库的约定、习惯用法和模式》

我同意您应该始终使用默认设置:抛出新ArgumentOutOfRangeException。还介绍了向枚举中添加新值的场景。可以说,NotSupportedException可能更合适,但见鬼,只要它抛出!同意!抛出新的OldshoeeException()比让逻辑失效并导致错误出现在其他地方要好。哦,你被抓了!:-)@马克:是的@添加。无耻的插件:我的助手Trinity实用程序类包括检查参数的扩展方法,包括枚举。哇,我没意识到C#enum实际上就是int。Java枚举真的好得多。@cletus:是的,的确。NET枚举根本不是OO:(请参阅,了解不应使用Enum.IsDefined的原因。好吧,如果(稍后)开始为枚举中未定义的枚举使用值,那么首先使用枚举有什么意义?重点是以后可以定义新的有效枚举值(通过重新编译代码)…但是根据您的代码编译的代码没有使用此新值进行测试…Enum.IsDefined只返回trueOops,您是对的-我返回并编辑了文章的一部分,但忘了更改另一部分。谢谢!
// Can't constrain T to be an enum, unfortunately. This will have to do :)
public static void ThrowIfNotDefined<T>(this T value, string name) where T : struct
{
    if (!Enum.IsDefined(typeof(T), value))
    {
        throw new ArgumentOutOfRangeException(name);
    }
}
public void Test(BookCategory cat)
{
    cat.ThrowIfNotDefined("cat");
}