在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的私有方法。 通过使用TryParse
和IsDefined
按顺序 ,我可以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");
如果你喜欢,可能值得使用来得到一个运行时错误。