在C#中枚举枚举枚举

有一些我在C#中无法理解的东西。 你可以将一个超范围的int转换成一个enum ,编译器不会退缩。 想象一下这个enum

 enum Colour { Red = 1, Green = 2, Blue = 3 } 

现在,如果你写:

 Colour eco; eco = (Colour)17; 

编译器认为这很好。 运行时也是如此。 呃?

为什么C#团队决定做到这一点? 这个决定忽略了在这种情况下使用枚举的观点:

 void DoSomethingWithColour(Colour eco) { //do something to eco. } 

在像C#这样的强types语言中,我想假设eco将始终保持合法的Colour值。 但这种情况并非如此。 程序员可以调用我的方法,赋值为17(如前面的代码片段),所以我的方法中的代码不能假定eco拥有合法的Colour值。 我需要明确地进行testing,并按照我的要求处理exception值。 为什么是这样?

在我看来,如果在编译时int值超出范围的情况下编译器发出错误(甚至是警告)消息将会好得多。 否则,运行时应该在赋值语句处引发exception。

你怎么看? 这是为什么?

(注意,这是我在博客上多年前发布的一个问题,但没有提供任何信息。

猜测“为什么”总是危险的,但考虑一下:

 enum Direction { North =1, East = 2, South = 4, West = 8 } Direction ne = Direction.North | Direction.East; int value = (int) ne; // value == 3 string text = ne.ToString(); // text == "3" 

[Flags]属性放在枚举的前面时,最后一行变为

 string text = ne.ToString(); // text == "North, East" 

不知道为什么,但我最近发现这个“function”非常有用。 我前几天写了这样的东西

 // a simple enum public enum TransmissionStatus { Success = 0, Failure = 1, Error = 2, } // a consumer of enum public class MyClass { public void ProcessTransmissionStatus (TransmissionStatus status) { ... // an exhaustive switch statement, but only if // enum remains the same switch (status) { case TransmissionStatus.Success: ... break; case TransmissionStatus.Failure: ... break; case TransmissionStatus.Error: ... break; // should never be called, unless enum is // extended - which is entirely possible! // remember, code defensively! future proof! default: throw new NotSupportedException (); break; } ... } } 

问题是,我如何testing最后一个案例? 假设有人可能扩展TransmissionStatus而不更新其消费者,就像上面可怜的小MyClass是完全合理的。 但是,我仍然想在这种情况下validation它的行为。 一种方法是使用铸造,如

 [Test] [ExpectedException (typeof (NotSupportedException))] public void Test_ProcessTransmissionStatus_ExtendedEnum () { MyClass myClass = new MyClass (); myClass.ProcessTransmissionStatus ((TransmissionStatus)(10)); } 

我当然可以看到塞萨尔的观点,我记得最初也让我感到困惑。 在我看来,在他们目前的实施中,枚举确实有点过低和漏洞。 在我看来,这个问题会有两个解决scheme。

1)只允许任意值存储在一个枚举,如果它的定义有FlagsAttribute。 通过这种方式,我们可以在适当的时候继续使用它们(并显式声明),但是当用作常量的占位符时,我们会在运行时检查值。

2)引入一个独立的基本types,称为say,bitmask,这将允许任何ulong值。 同样,我们仅将标准枚举限制为声明的值。 这可以让编译器为你分配比特值。 所以这:

 [Flags] enum MyBitmask { FirstValue = 1, SecondValue = 2, ThirdValue = 4, FourthValue = 8 } 

将等同于:

 bitmask MyBitmask { FirstValue, SecondValue, ThirdValue, FourthValue } 

毕竟,任何位掩码的值是完全可以预测的,对吧? 作为一名程序员,我非常乐意将这些细节抽象出来。

不过,现在已经太晚了,我想我们一直坚持目前的实施。 :/

你不需要处理exception。 该方法的先决条件是调用者应该使用枚举,而不是在所述枚举中input任何不合逻辑的东西。 那将是疯狂的。 不是枚举点使用整数?

任何投入Color枚举17的开发者,就我而言,都需要踢17个。

这是为什么你永远不应该分配整数值到你的枚举的原因之一。 如果他们有重要的价值,需要在代码的其他部分使用将枚举变成一个对象。

这是意想不到的…我们真正想要的是控制铸造…例如:

 Colour eco; if(Enum.TryParse("17", out eco)) //Parse successfully?? { var isValid = Enum.GetValues(typeof (Colour)).Cast<Colour>().Contains(eco); if(isValid) { //It is really a valid Enum Colour. Here is safe! } } 

当你定义一个枚举时,你实际上是给名称赋值(如果你愿意的话,可以使用合成糖)。 当您将17投射到“颜色”时,您将为没有名称的“颜色”存储一个值。 正如你可能知道最后它只是一个int字段。

这类似于声明一个只能接受1到100的整数的整数。 我见过的唯一支持这种检查的语言是CHILL。

 if (!Enum.IsDefined(typeof(Colour), 17)) { // Do something } 

简洁版本:

不要这样做。

尝试将枚举更改为整数只允许有效的值(也许是一个默认的回退)需要帮助方法。 在这一点上,你没有一个枚举 – 你真的有一个类。

如果整数是重要的,那么就是双倍的 – 就像Bryan Rowe所说的那样。

以为我会分享我最终使用的代码来validation枚举,因为到目前为止,我们似乎没有任何东西在这里工作…

 public static class EnumHelper<T> { public static bool IsValidValue(int value) { try { Parse(value.ToString()); } catch { return false; } return true; } public static T Parse(string value) { var values = GetValues(); int valueAsInt; var isInteger = Int32.TryParse(value, out valueAsInt); if(!values.Select(v => v.ToString()).Contains(value) && (!isInteger || !values.Select(v => Convert.ToInt32(v)).Contains(valueAsInt))) { throw new ArgumentException("Value '" + value + "' is not a valid value for " + typeof(T)); } return (T)Enum.Parse(typeof(T), value); } public static bool TryParse(string value, out T p) { try { p = Parse(value); return true; } catch (Exception) { p = default(T); return false; } } public static IEnumerable<T> GetValues() { return Enum.GetValues(typeof (T)).Cast<T>(); } } 

老问题,但最近这让我困惑,而Google把我带到了这里。 我find了一篇文章,进一步search,最终让我点击,并认为我会回来分享。

要点是Enum是一个结构,这意味着它是一个值types( 源 )。 但实质上,它充当底层types(int,byte,long等)的派生types。 所以,如果你可以把Enumtypes看作与它的基础types相同的东西,并且增加了一些function/语法糖(就像Otávio所说的那样),那么你就会意识到这个“问题”,并且能够防范它。

说到这一点,这里是我写的一个扩展方法的核心,可以轻松地将事物转换/parsing为Enum:

 if (value != null) { TEnum result; if (Enum.TryParse(value.ToString(), true, out result)) { // since an out-of-range int can be cast to TEnum, double-check that result is valid if (Enum.IsDefined(typeof(TEnum), result.ToString())) { return result; } } } // deal with null and defaults... 

这个variables的value是对象types的,因为这个扩展有一个接受int,int ?, string,Enum和Enum?的“重载”。 他们都被装箱并发送到parsing的私有方法。 通过使用TryParseIsDefined 按顺序 ,我可以parsing刚刚提到的所有types,包括混合大小写string,它非常强大。

用法如此(假定NUnit):

 [Test] public void MultipleInputTypeSample() { int source; SampleEnum result; // valid int value source = 0; result = source.ParseToEnum<SampleEnum>(); Assert.That(result, Is.EqualTo(SampleEnum.Value1)); // out of range int value source = 15; Assert.Throws<ArgumentException>(() => source.ParseToEnum<SampleEnum>()); // out of range int with default provided source = 30; result = source.ParseToEnum<SampleEnum>(SampleEnum.Value2); Assert.That(result, Is.EqualTo(SampleEnum.Value2)); } private enum SampleEnum { Value1, Value2 } 

希望能帮助别人。

免责声明:我们不使用标志/位掩码枚举…这尚未经过使用情况下的testing,可能不会工作。

它会通过使用Enum.Parse();

 Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17"); 

如果你喜欢,可能值得使用来得到一个运行时错误。