为什么C#编译器允许空枚举?


73

我今天不小心定义了一个不包含任何值的枚举。像这样一个例子:

public enum MyConfusingEnum{}

编译器很高兴让我定义它并成功构建了代码。

现在我显然不能使用传统意义上的代码,因为代码..

var mySadCompiler = MyConfusingEnum;

没有指定值,但有趣的是我能说,..

var myRoundTheHousesZeroState = Activator.CreateInstance<MyConfusingEnum>();

正如我提到的那样,它是MyConfusingEnum值为0的值类型;

我的问题是,为什么编译器允许空定义,并且在任何情况下它都可能有用?


10
我确定答案是“因为规范定义了该行为”。但当然,下一个合乎逻辑的问题是...为什么委员会决定该行为....
林恩摇摇欲坠

11
我认为您可以做到var x = (MyConfusingEnum)0;
John Alexiou 2014年

@ ja72-的确可以。每天都是上学的日子吗?您可以做些不直观的事情var x = (MyConfusingEnum)99
八十一联合

7
处理过会产生错误的VB .NET之后,我会说无法声明一个空的枚举(因为我还没有查找这些值)而感到恼火。
约书亚2014年

Answers:


91

首先,你可能已经做了很多更容易:

MyConfusingEnum x1 = 0;
MyConfusingEnum x2 = default(MyConfusingEnum);
MyConfusingEnum x3 = new MyConfusingEnum();
MyConfusingEnum x4 = (MyConfusingEnum) 123;

以上所有工作都很好。(您可能会对第一种方法感到惊讶;有关详细信息,请参见有关隐式枚举转换的规范部分。)

我的问题是为什么编译器允许空定义

首先,用一个问题回答您的问题。您还会拒绝编译器吗?

class C {}
interface I {}
struct S {}

为什么或者为什么不?

更直接地不回答您的问题:“为什么世界没有不同?” 问题很难回答。我将不回答这个不可能的问题,而是回答“假设使空枚举出错,并向设计团队提出了错误;您将如何应对这个问题?” 这个问题仍然是事实,但至少我可以回答。

问题在于,该功能的成本是否可以通过其利益得到证明。

为了工作,必须考虑,设计,指定,实现,测试,记录和交付给客户的语言功能。这是“产生错误”的功能,因此必须将错误消息编写并翻译成数十种语言,文档也必须如此。我花了五分钟的时间来实现该功能,这使许多人获得了很多工作,而他们的收入却很高。

但是,这实际上并不是相关费用。的机会成本是相关成本。预算是有限的,功能不是免费的,因此实现的任何功能都意味着必须削减一些其他功能。您想要削减C#的哪个功能以使用此功能?由于无法做得更好而失去的利益是机会成本

我还注意到,您提出的功能对任何人没有明显的好处,这对设计委员会来说是很难的。也许我没有看到引人注目的好处;如果是这样,那是什么?

有没有可能有用的方案?

没有人想到。“拒绝没有明显用处的程序”不是C#的设计目标。


7
只是一个注释:谈论空接口->标记接口
Silviu Burcea 2014年

7
@SilviuBurcea:Microsoft建议使用属性,而不是标记接口。请参阅CA1040:避免使用空接口
Brian

12
令人困惑的是,VB.NET不允许您声明一个空的枚举!!
2014年

25
好答案。 tl; dr: “拒绝那些显然没有用的程序不是C#的设计目标。”
罗伯特·哈维

12
@Alvaro:有趣;我不知道。您必须问VB团队,如果您好奇的话,为什么会这样,因为我不知道该功能的合理性。再说一次,VB有很多奇怪的小功能。从历史上看,它具有更多……我们应该说自由主义的态度,是的,自由主义是好的……对增加许多小功能。
埃里克·利珀特

17

您可以将任何基础整数类型的值(int默认情况下)转换为枚举-因此(MyConfusingEnum)42现在将属于该枚举类型。

一般而言,我不认为这是个好主意,但是在某些情况下,“枚举”值来自外部源,而代码看起来更适合enum

示例(假设代码在Enum中封装了一些“基于int的状态”:

enum ExternalDeviceState {};

ExternalDeviceState GetState(){ ... return (ExternalDeviceState )intState;}
bool IsDeviceStillOk(ExternalDeviceState currentState) { .... }

规范确实允许空枚举:

14.1枚举声明

枚举声明声明了新的枚举类型。枚举声明以关键字enum开头,并定义了名称,可访问性,基础类型和枚举成员。

enum-declaration:
   attributesopt   enum-modifiersopt   enum   identifier
        enum-base(opt)   enum-body   ;(opt)

enum-base:
:   integral-type

enum-body:
  {   enum-member-declarations(opt)   }  
  {   enum-member-declarations   ,   }

请注意,enum-member-declarations(opt)其中没有任何内容,已将其明确标记为variant {}


14

Activator.CreateInstance<MyConfusingEnum>();与相同new MyConfusingEnum()。(docs

调用枚举的构造函数会为您带来0价值。

由于设计决策,枚举可以具有对于后备类型有效的任何值(通常是int),而不必是枚举中定义的值。

出于该设计决策的原因,我可以在标题为“为什么将int强制转换为无效的枚举值时不抛出异常?”的问题上指出这个答案

@AlexeiLevenkov提供了允许使用空枚举的规范,我们可以猜测其基本原理是,由于任何后备类型值都是有效的,因此允许使用空枚举。


7

有没有可能有用的方案?

正如其他人已经提到的那样,您可以通过简单的强制转换将基础类型允许的任何值分配给该枚举。这样,在int会使事情变得混乱的情况下,您可以强制执行类型检查。例如:

public enum Argb : int {}

public void SetColor(Argb a) { ....

或者您希望拥有一些扩展方法而又不影响int数据类型

public static Color GetColor(this Argb value) {
    return new Color( (int)value );
}

public static void Deconstruct( this Argb color, out byte alpha, out byte red, out byte green, out byte blue ) {
        alpha = (byte)( (uint)color >> 24 );
        red = (byte)( (uint)color >> 16 );
        green = (byte)( (uint)color >> 8 );
        blue = (byte)color;
}

并用作

var (alpha, red, green, blue) = color;

public enum Argb : int { NOTHING_TO_SEE_HERE }工作良好。
Jim Balter

3

有没有可能有用的方案?

我在Java世界中经历过。

对于枚举,您在编译时知道所有可能的值

但是,在编译之前会有一段时间,您可能还不知道所有值,或者根本不打算实现任何值。

在设计API的过程中,我实现了没有任何值的枚举,以允许从其他接口引用它。我稍后再添加值。


enum foo { NOTHING_DEFINED_HERE_YET }效果很好。
Jim Balter
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.