Waarom staat de C#-compiler lege opsommingen toe?

Ik heb vandaag per ongeluk een opsomming gedefinieerd die geen waarden bevatte. Zoals deze bijvoorbeeld:

public enum MyConfusingEnum{}

De compiler was heel blij me dat te laten definiëren en de code met succes te laten bouwen.

Nu kan ik dat natuurlijk niet meer in de traditionele zin gebruiken sinds de code,..

var mySadCompiler = MyConfusingEnum;

geeft geen waarde op, maar interessant genoeg konzeggen,..

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

wat, zoals ik al zei, een waardetype is van MyConfusingEnummet de waarde 0;

Mijn vraag is waarom de compiler een lege definitie toestaat en zijn er scenario’s waarin dit nuttig zou kunnen zijn?


Antwoord 1, autoriteit 100%

Ten eerste had u dat veelgemakkelijker kunnen doen:

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

al het bovenstaande werkt prima. (Het zal je misschien verbazen dat de eerste werkt; zie de specificatiesectie over impliciete opsommingsconversies voor details.)

Mijn vraag is waarom de compiler een lege definitie toestaat

Ik zal beginnen met het beantwoorden van uw vraag met een vraag. Wilt u de compiler ook laten afwijzen?

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

Waarom wel of niet?

Om directer je vraag niet te beantwoorden: “waarom is de wereld niet anders dan hij is?” vragen zijn moeilijk te beantwoorden. In plaats van die onmogelijke vraag te beantwoorden, zal ik de vraag beantwoorden “stel dat het maken van lege opsommingen een fout was gepitcht aan het ontwerpteam; hoe zou u op die pitch hebben gereageerd?” Die vraag is nog steeds contrafeitelijkmaar ik kan er tenminste een beantwoorden.

De vraag wordt dan of de kostenvan de functie worden gerechtvaardigd door de voordelen.

Om te kunnen werken, moet een taalfunctie worden bedacht, ontworpen, gespecificeerd, geïmplementeerd, getest, gedocumenteerd en naar klanten worden verzonden. Dit is een “maak een fout”-functie, dus de foutmelding moet worden geschreven en vertaald in een tiental talen, net als de documentatie. De vijf minuten die het me zou hebben gekost om de functie te implementeren, vertalen zich in vele uren werk door veel mensen die nogal veel betaald krijgen.

Dat zijn echter niet de relevante kosten. De opportuniteitskostenzijn de relevante kosten. Budgetten zijn eindig, functies zijn niet gratis, en daarom betekent elke geïmplementeerde functie dat er op een andere functie moet worden gekort; in welke functie van C# zou je willen dat deze geknipt zou worden om deze functie erin te krijgen? Het verlorenvoordeel van het nietkunnen doen van een betere functie zijn de opportuniteitskosten.

Ik merk ook op dat uw voorgestelde functie nul duidelijk voordeel heeft voor iedereen, wat het moeilijk zou maken om het aan de ontwerpcommissie te verkopen. Misschien is er een overtuigend voordeel dat ik niet zie; zo ja, wat is het?

zijn er scenario’s waarin het nuttig kan zijn?

Niets komt in me op. “Programma’s afwijzen die niet duidelijk nuttig zijn” is geen ontwerpdoel van C#.


Antwoord 2, autoriteit 19%

Je kunt elke waarde van het onderliggende integer-type casten (ik denk standaard int) naar de enum – dus (MyConfusingEnum)42zal nu van dat enum-type zijn.

Ik denk niet dat het in het algemeen een goed idee is, maar er kunnen gevallen zijn waarin “enum”-waarden afkomstig zijn van een externe bron en de code er gewoon mooier uitziet met enum.

Voorbeeld (ervan uitgaande dat code een “int-based state” in Enum inkapselt:

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

Specificatie staat inderdaad een lege opsomming toe:

14.1 Enum-aangiften

Een enum-declaratie declareert een nieuw enum-type. Een enum-declaratie begint met het trefwoord enum en definieert de naam, de toegankelijkheid, het onderliggende type en de leden van de 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   ,   }

Merk op dat enum-member-declarations(opt)expliciet is gemarkeerd als variant waarbij niets in {}staat.


Antwoord 3, autoriteit 15%

Activator.CreateInstance<MyConfusingEnum>();is hetzelfde als new MyConfusingEnum(). (docs)

Als je de constructor van een enum aanroept, krijg je 0als waarde.

Vanwege een ontwerpbeslissing kan een enum elke waarde hebben die geldig is voor het backing-type (meestal int), het hoeft geen waarde te zijn die in de enum is gedefinieerd.

p>

Vanwege die ontwerpbeslissing kan ik u verwijzen naar dit antwoordop een vraag met de titel ” Waarom veroorzaakt het casten int naar de ongeldige enumwaarde GEEN uitzondering?”

@AlexeiLevenkov heeft de specificatie geleverd die een lege opsomming toestaat, we kunnen raden dat de reden hiervoor is dat aangezien elke waarde van het backing-type geldig is, een lege opsomming is toegestaan.


Antwoord 4, autoriteit 8%

zijn er scenario’s waarin het nuttig kan zijn?

Zoals anderen al hebben vermeld, kunt u aan deze opsomming elke waarde toewijzen die het onderliggende type toestaat door een eenvoudige cast.
Op die manier kun je Type Checking afdwingen, in situaties waar een int verwarrend zou zijn. Bijvoorbeeld:

public enum Argb : int {}
public void SetColor(Argb a) { ....

of je wilt een aantal uitbreidingsmethoden hebben zonder het datatype int te vervuilen, met hen

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

en gebruik het als

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

Antwoord 5, autoriteit 3%

Zijn er scenario’s waarin het nuttig kan zijn?

Ik heb er een meegemaakt in de Java-wereld.

In het geval van opsommingen, weet je alle mogelijke waarden tijdens het compileren.

Er is echter een tijd vóór het compileren waarin u (nog) niet alle waarden kent of waarin u eenvoudigweg nog niet van plan bent waarden te implementeren.

Tijdens het ontwerpen van een API heb ik een opsomming geïmplementeerd zonder waarden om ernaar te kunnen verwijzen vanuit andere interfaces. Ik heb later waarden toegevoegd.

Other episodes