Warum erlaubt der C # -Compiler leere Aufzählungen?

73

Ich habe heute versehentlich eine Aufzählung definiert, die keine Werte enthielt. Wie dieser zum Beispiel:

public enum MyConfusingEnum{}

Der Compiler war sehr froh, dass ich das definieren und den Code erfolgreich erstellen konnte.

Jetzt kann ich das offensichtlich nicht im herkömmlichen Sinne verwenden, da der Code, ..

var mySadCompiler = MyConfusingEnum;

keinen Wert angeben , aber interessanterweise ich war die Lage zu sagen, ..

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

was, wie ich angedeutet habe, ein Werttyp MyConfusingEnummit dem Wert 0 ist;

Meine Frage ist, warum der Compiler eine leere Definition zulässt und gibt es Szenarien, in denen dies nützlich sein könnte?

EightyOne Unite
quelle
10
Ich bin sicher, dass die Antwort "weil die Spezifikation dieses Verhalten definiert" lautet. Aber natürlich ist die nächste logische Frage ... warum hat das Komitee über dieses Verhalten entschieden ...
Lynn Crumbling
11
Ich denke, Sie können tunvar x = (MyConfusingEnum)0;
John Alexiou
@ ja72 - Das kannst du ja. Jeder Tag ist ein Schultag, oder? Irgendwie nicht intuitiv, was Sie aber tun können var x = (MyConfusingEnum)99.
EightyOne Unite
7
Nachdem ich mich mit VB .NET befasst habe, bei dem dies zu einem Fehler führt, würde ich sagen, dass es leicht ärgerlich ist, keine leere Aufzählung deklarieren zu können (weil ich die Werte noch nicht nachgeschlagen habe).
Joshua

Antworten:

91

Zunächst einmal hätten Sie das viel einfacher tun können:

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

Alle oben genannten funktionieren gut. (Sie werden überrascht sein, dass das erste funktioniert. Weitere Informationen finden Sie im Abschnitt mit den Spezifikationen für implizite Aufzählungskonvertierungen.)

Meine Frage ist, warum der Compiler eine leere Definition zulässt

Ich beantworte Ihre Frage zunächst mit einer Frage. Würden Sie auch den Compiler ablehnen lassen?

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

Warum oder warum nicht?

Um Ihre Frage direkter nicht zu beantworten: "Warum ist die Welt nicht anders als sie ist?" Fragen sind schwer zu beantworten. Anstatt diese unmögliche Frage zu beantworten, beantworte ich die Frage: "Angenommen, beim Erstellen leerer Aufzählungen wurde dem Designteam ein Fehler gemeldet. Wie hätten Sie auf diese Frage reagiert?" Diese Frage ist immer noch kontrafaktisch, aber zumindest kann ich sie beantworten.

Es stellt sich dann die Frage, ob die Kosten des Features durch seine Vorteile gerechtfertigt sind .

Um zu funktionieren, muss ein Sprachfeature gedacht, entworfen, spezifiziert, implementiert, getestet, dokumentiert und an Kunden versendet werden. Dies ist eine Funktion "Fehler erzeugen", daher muss die Fehlermeldung geschrieben und in einige Dutzend Sprachen übersetzt werden, ebenso wie die Dokumentation. Die fünf Minuten, die ich für die Implementierung der Funktion benötigt hätte, bedeuten für viele Leute, die ziemlich viel bezahlt werden, viele Arbeitsstunden.

Dies sind jedoch nicht die relevanten Kosten. Die Opportunitätskosten sind die relevanten Kosten. Die Budgets sind begrenzt, Funktionen sind nicht kostenlos, und daher bedeutet jede implementierte Funktion, dass eine andere Funktion gekürzt werden muss. Welches Feature von C # möchten Sie geschnitten haben, um dieses Feature zu erhalten? Der verlorene Nutzen aus nicht in der Lage , eine bessere Funktion zu tun ist , die Opportunitätskosten .

Ich stelle auch fest, dass Ihre vorgeschlagene Funktion für niemanden einen offensichtlichen Nutzen hat , was es für das Designkomitee schwierig machen würde, sie zu verkaufen. Vielleicht gibt es einen überzeugenden Vorteil, den ich nicht sehe; wenn ja, was ist das?

Gibt es Szenarien, in denen dies nützlich sein könnte?

Niemand fällt mir ein. "Programme ablehnen, die offensichtlich nicht nützlich sind" ist kein Entwurfsziel von C #.

Eric Lippert
quelle
7
Nur eine Anmerkung: über leere Schnittstelle sprechen -> Marker-Schnittstelle
Silviu Burcea
7
@SilviuBurcea: Microsoft empfiehlt die Verwendung von Attributen anstelle von Markierungsschnittstellen. Siehe CA1040: Vermeiden Sie leere Schnittstellen
Brian
12
Verwirrenderweise erlaubt VB.NET nicht, eine leere Aufzählung zu deklarieren !!
Alvaro
25
Gute Antwort. tl; dr: "Das Ablehnen von Programmen, die offensichtlich nicht nützlich sind, ist kein Entwurfsziel von C #."
Robert Harvey
12
@ Alvaro: Interessant; Das wusste ich nicht. Sie müssen das VB-Team fragen, warum das so ist, wenn Sie neugierig sind, weil ich nicht weiß, was die Rechtfertigung für diese Funktion ist. Andererseits hat VB viele seltsame kleine Merkmale; Es hat historisch gesehen mehr ... sollen wir sagen, liberale Haltung, ja, liberal ist gut ... gegenüber dem Hinzufügen vieler kleiner Merkmale.
Eric Lippert
17

Sie können jeden Wert des zugrunde liegenden Integer-Typs (glaube ich intstandardmäßig) in die Aufzählung umwandeln - also wird er (MyConfusingEnum)42jetzt von diesem Aufzählungstyp sein.

Ich denke nicht, dass es im Allgemeinen eine gute Idee ist, aber es könnte Fälle geben, in denen "Enum" -Werte von einer externen Quelle stammen und Code einfach besser aussieht enum.

Beispiel (unter der Annahme, dass Code einen "int-basierten Status" in Enum enthält:

enum ExternalDeviceState {};

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

Die Spezifikation erlaubt in der Tat eine leere Aufzählung:

14.1 Aufzählungserklärungen

Eine Aufzählungsdeklaration deklariert einen neuen Aufzählungstyp. Eine Aufzählungsdeklaration beginnt mit dem Schlüsselwort Aufzählung und definiert den Namen, die Zugänglichkeit, den zugrunde liegenden Typ und die Mitglieder der Aufzählung.

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

Beachten Sie, dass dies enum-member-declarations(opt)explizit als Variante markiert ist, in der sich nichts befindet {}.

Alexei Levenkov
quelle
14

Activator.CreateInstance<MyConfusingEnum>();ist das gleiche wie new MyConfusingEnum(). ( docs )

Wenn Sie den Konstruktor einer Aufzählung aufrufen, erhalten Sie einen 0Wert.

Aufgrund einer Entwurfsentscheidung kann eine Aufzählung einen beliebigen Wert haben, der für den Hintergrundtyp (normalerweise int) gültig ist. Es muss sich nicht um einen in der Aufzählung definierten Wert handeln.

Aufgrund dieser Entwurfsentscheidung kann ich Sie auf diese Antwort mit der Frage "Warum löst das Casting von int in einen ungültigen Aufzählungswert KEINE Ausnahme aus?" Verweisen.

@AlexeiLevenkov hat die Spezifikation bereitgestellt, die eine leere Aufzählung zulässt. Wir können davon ausgehen, dass eine leere Aufzählung zulässig ist, da jeder Wert für den Hintergrundtyp gültig ist.

user247702
quelle
7

Gibt es Szenarien, in denen dies nützlich sein könnte?

Wie bereits erwähnt, können Sie dieser Aufzählung jeden Wert zuweisen, den der zugrunde liegende Typ durch eine einfache Umwandlung zulässt. Auf diese Weise können Sie die Typprüfung in Situationen erzwingen, in denen ein int die Sache verwirren würde. Zum Beispiel:

public enum Argb : int {}

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

oder Sie möchten einige Erweiterungsmethoden haben, ohne den int-Datentyp damit zu überladen

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

und benutze es als

var (alpha, red, green, blue) = color;
Panos Theof
quelle
public enum Argb : int { NOTHING_TO_SEE_HERE }funktioniert gut.
Jim Balter
3

Gibt es Szenarien, in denen dies nützlich sein könnte?

Ich habe einen in der Java-Welt erlebt.

Bei Aufzählungen kennen Sie alle möglichen Werte zur Kompilierungszeit .

Es gibt jedoch eine Zeit vor der Kompilierungszeit, in der Sie möglicherweise (noch) nicht alle Werte kennen oder in der Sie einfach noch keine Werte implementieren möchten.

Während des Entwurfs einer API habe ich eine Aufzählung ohne Werte implementiert, um die Referenzierung von anderen Schnittstellen aus zu ermöglichen. Ich habe später Werte hinzugefügt.

Aboger
quelle
enum foo { NOTHING_DEFINED_HERE_YET }funktioniert gut.
Jim Balter