Why does the C# compiler allow empty enums?

C#Compiler ConstructionEnums

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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 - C#

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 {}.

Solution 3 - C#

Activator.CreateInstance<MyConfusingEnum>(); is the same as new MyConfusingEnum(). (docs)

Calling the constructor of an enum gives you 0 as value.

Because of a design decision, an enum can have any value that is valid for the backing type (usually int), it doesn't have to be a value defined in the enum.

For the reason of that design decision, I can point you to this answer on a question titled "Why does casting int to invalid enum value NOT throw exception?"

@AlexeiLevenkov has provided the spec that allows an empty enum, we can guess that the rationale for this is that since any backing type value is valid, an empty enum is allowed.

Solution 4 - C#

> are there any scenarios where it could be useful?

As other have already mention you can assign to this enum any value that the underlying type permits by a simple cast. That way you can enforce Type Checking, in situations where an int would make thing confusing. For example:

public enum Argb : int {}

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

or you want to have some extension methods without clutering the int datatype, with them

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;
}

and use it as

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

Solution 5 - C#

>> Are there any scenarios where it could be useful?

I experienced one in the Java world.

>> In case of enums, you know all possible values at compile time.

However, there is a time before compile time in which you may not know all values (yet) or in which you simply do not intend to implement any values, yet.

In the course of designing an API, I implemented an enumeration without any values to enable referencing it from other interfaces. I added values a later time.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEightyOne UniteView Question on Stackoverflow
Solution 1 - C#Eric LippertView Answer on Stackoverflow
Solution 2 - C#Alexei LevenkovView Answer on Stackoverflow
Solution 3 - C#user247702View Answer on Stackoverflow
Solution 4 - C#Panos TheofView Answer on Stackoverflow
Solution 5 - C#abogerView Answer on Stackoverflow