Why does the C# compiler allow empty enums?

I accidentally defined an enum today that contained no values. Like this one for example:

public enum MyConfusingEnum{}

The compiler was quite happy to let me define that and the code built successfully.

Now I obviously can't use that in the traditional sense since the code,..

var mySadCompiler = MyConfusingEnum;

doesn't specify a value but interestingly enough I was able to say,..

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

which, as I've alluded to, is a value type of MyConfusingEnum with the value 0;

My question is why does the compiler allow an empty definition and are there any scenarios where it could be useful?


Solution 1:

First off, you could have done that much more easily:

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

all of the above work just fine. (You may be surprised that the first works; see the specification section on implicit enumeration conversions for details.)

My question is why does the compiler allow an empty definition

I'll begin by answering your question with a question. Would you also have the compiler reject?

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

Why or why not?

To more directly not answer your question: "why is the world not different than it is?" questions are hard to answer. Instead of answering that impossible question, I'll answer the question "suppose making empty enums an error had been pitched to the design team; how would you have responded to that pitch?" That question is still counterfactual but at least it is one I can answer.

The question then becomes whether the cost of the feature is justified by its benefits.

In order to work a language feature must be thought of, designed, specified, implemeted, tested, documented and shipped to customers. This is a "produce an error" feature, so the error message must be written and translated into some dozen languages, as must the documentation. The five minutes it would have taken me to implement the feature translate into many hours of work by many people who are paid rather a lot.

However, that is not actually the relevant cost. The opportunity cost is the relevant cost. Budgets are finite, features are not free, and therefore any feature which is implemented means some other feature must be cut; which feature of C# would you like to have been cut to get this feature in? The lost benefit from not being able to do a better feature is the opportunity cost.

I note also that your proposed feature has zero obvious benefit to anyone, which would make it a tough sell to the design committee. Perhaps there is a compelling benefit that I'm not seeing; if so, what is it?

are there any scenarios where it could be useful?

None come to mind. "Rejecting programs which are not obviously useful" is not a design goal of C#.

Solution 2:

You can cast any value of underlying integer type (I think int by default) to the enum - so (MyConfusingEnum)42 now will be of that enum type.

I don't think it is good idea in general, but there could be cases when "enum" values are coming from external source and code just looks nicer with enum.

Sample (assuming code incapsulates some "int-based state" in Enum:

enum ExternalDeviceState {};

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

Specification does indeed allow empty enum:

14.1 Enum declarations

An enum declaration declares a new enum type. An enum declaration begins with the keyword enum, and defines the name, accessibility, underlying type, and members of the 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   ,   }

Note that enum-member-declarations(opt) is explicitly marked as variant where nothing is inside {}.