Perché posso assegnare 0,0 ai valori di enumerazione, ma non 1,0


90

Solo per curiosità: perché posso assegnare 0.0 a una variabile che è di tipo enumerativo, ma non 1.0? Dai un'occhiata al seguente codice:

public enum Foo
{
    Bar,
    Baz
}

class Program
{
    static void Main()
    {
        Foo value1 = 0.0;
        Foo value2 = 1.0;   // This line does not compile
        Foo value3 = 4.2;   // This line does not compile
    }
}

Ho pensato che le conversioni tra tipi numerici e valori di enumerazione fossero consentite solo tramite cast? Cioè potrei scrivere Foo in value2 = (Foo) 1.0;modo che la riga 2 Mainpossa essere compilata. Perché c'è un'eccezione per il valore 0.0in C #?


17
Per me è strano che tu possa assegnare il doppio letterale 0.0 all'enumerazione personalizzata. Non che non sia possibile assegnare un valore 1.0letterale all'enumerazione personalizzata.
Ilya Ivanov

2
Sospetto che il compilatore lo tratti come 0invece. Una volta ho avuto una domanda simile e Rawling ha pubblicato qui un'ottima risposta .
Amichevole

2
IdeOne non lo compila.
Johnny Mopp

Risposte:


98

È un bug che puoi usare 0.0. Il compilatore tratta implicitamente tutte le espressioni costanti con un valore zero come solo 0.

Ora, è corretto che il compilatore consenta una conversione implicita da intun'espressione costante di 0 alla tua enum secondo la sezione 6.1.3 della specifica C # 5:

Una conversione di enumerazione implicita consente di convertire il valore letterale intero decimale 0 in qualsiasi tipo enum e in qualsiasi tipo nullable il cui tipo sottostante è un tipo enum. In quest'ultimo caso la conversione viene valutata convertendola nel sottostante tipo enum e avvolgendo il risultato (§4.1.10).

Ne ho già parlato con il team C #: avrebbero voluto rimuovere la conversione accidentale da 0.0 (e in effetti 0.0m e 0.0f) ai valori enum, ma sfortunatamente ho capito che ha rotto troppo codice, anche se in primo luogo non avrebbe mai dovuto essere consentito.

Mono mcscompilatore vieta tutte queste conversioni virgola mobile, anche se non permette:

const int Zero = 0;
...

SomeEnum x = Zero;

nonostante Zerosia un'espressione costante ma non un numero intero decimale letterale.

Non sarei sorpreso di vedere la modifica della specifica C # in futuro per consentire qualsiasi espressione costante intera con un valore di 0 (cioè per imitare mcs), ma non mi aspetto che le conversioni in virgola mobile siano mai ufficialmente corrette. (Mi sono sbagliato prima sulla previsione del futuro di C #, ovviamente ...)


3
Secondo le specifiche, deve essere solo il letterale 0. Quindi dovrebbe rifiutare 1-1: intun'espressione costante con un valore di 0. Ma come osservi, il compilatore non è in linea con le specifiche qui.
Damien_The_Unbeliever

4
it broke too much code- è davvero difficile immaginare un motivo per scrivere tale codice.
Ilya Ivanov

1
@ObsidianPhoenix: non sono sicuro di cosa intendi. E 'esattamente equivalente a: SomeEnum x = (SomeEnum) 0;. Questo è il caso che esista o meno un valore zero denominato.
Jon Skeet

2
@ObsidianPhoenix: Beh no, perché il valore di Test.Fooè 1, non 0 ... di nuovo, è esattamente lo stesso di se avessi scritto Test v1 = (Test) 0;- e quel comportamento vale per qualsiasi valore che non è un valore denominato nell'enumerazione.
Jon Skeet

2
@ JonSkeet verrà risolto a Roslyn?
Max

98

La risposta di Jon è corretta. Aggiungo i seguenti punti.

  • Ho causato questo bug stupido e imbarazzante. Molte scuse.

  • Il bug è stato causato dalla mia incomprensione della semantica di un predicato "expression is zero" nel compilatore; Credevo stesse controllando solo l'uguaglianza di zero interi, quando in realtà stava controllando di più sulla falsariga di "è questo il valore predefinito di questo tipo?" Infatti, in una versione precedente del bug era effettivamente possibile assegnare il valore predefinito di qualsiasi tipo a un enum! Ora sono solo i valori predefiniti dei numeri. (Lezione: assegna un nome ai predicati di supporto con attenzione.)

  • Il comportamento che stavo tentando di implementare che ho incasinato era in realtà una soluzione alternativa per un bug leggermente diverso. Puoi leggere l'intera terribile storia qui: https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-one e https://docs.microsoft .com / en-us / archive / blogs / ericlippert / the-root-of-all-evil-part-two (Lezione: è molto facile introdurre nuovi bug peggiori mentre si risolvono quelli vecchi.)

  • Il team di C # ha deciso di sancire questo comportamento bug piuttosto che risolverlo perché il rischio di rompere il codice esistente senza alcun vantaggio convincente era troppo alto. (Lezione: fallo bene la prima volta!)

  • Il codice che ho scritto in Roslyn per preservare questo comportamento può essere trovato nel metodo IsConstantNumericZeroin https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs - guardalo per maggiori dettagli su cosa sia esattamente il comportamento di Roslyn. Ho scritto quasi tutto il codice nella directory Conversions; Ti incoraggio a leggere tutto perché ci sono molti fatti interessanti su come C # diverge dalle specifiche nei commenti. Ho decorato ciascuno con SPEC VIOLATION per renderli facili da trovare.

Un altro punto di interesse: C # consente anche di utilizzare qualsiasi valore enum in un inizializzatore enum indipendentemente dalla sua zeroness:

enum E { A = 1 }
enum F { B = E.A }  // ???

Le specifiche sono alquanto vaghe sul fatto che questo debba essere legale o meno, ma ancora una volta, poiché questo è stato nel compilatore da molto tempo, è probabile che i nuovi compilatori mantengano il comportamento.


10
È davvero fantastico, finalmente riesco a vedere il codice che hai scritto. È fantastico che il codice sorgente di Roslyn sia open source. Ora capisco perfettamente che esistono validi motivi (tecnici / legali) per non fornire la cronologia delle modifiche, ma sarebbe stato fantastico vedere la cronologia delle modifiche per vedere come si è evoluto il codice.
Soluzione

The C# team decided to enshrine this buggy behaviour rather than fixing it because the risk of breaking existing code for no compelling benefit was too high.Non credo che ci sarebbero molte persone che si affidano a questo comportamento, ed è una di queste stranezze che forse sarebbe stato meglio risolvere. Tuttavia, non fa nemmeno molto male (tranne che per i progetti che implementano le specifiche).
Aidiakapi

5
@Aidiakapi: In effetti, il numero di persone colpite dovrebbe essere piccolo; non è zero. Il team C # prende molto sul serio le modifiche di rilievo. È facile per te dire che è meglio fare la correzione; non devi avere a che fare con clienti arrabbiati che chiamano il tuo vicepresidente per lamentarsi del fatto che il tuo banale cambiamento che non aggiunge alcun vantaggio ha ritardato di un giorno l'integrazione del sistema.
Eric Lippert

3
La situazione peggiora. Tutte queste modifiche sostanziali saranno (idealmente) elencate nella guida alla migrazione di Framework di Microsoft. Più questo elenco è lungo, più gli utenti esitano a migrare la loro applicazione. Quindi, anche un cambiamento di interruzione minore causa: 1. L'interruzione di un piccolo numero di applicazioni. 2. Un numero limitato di utenti si rifiuta di eseguire l'aggiornamento (anche se il problema non li riguarda). 3. Un piccolo numero di utenti spreca risorse per valutare se la modifica sostanziale li riguarda. 4. Gli utenti di # 1, # 2 e # 3 si lamentano con tutti gli altri.
Brian

@EricLippert Se "La specifica è un po 'vaga", non avrebbe senso aggiornare la specifica? (Domanda autentica!)
James

10

Le enumerazioni in C # sono per definizione valori integrali. Per coerenza, il C # non dovrebbe accettare nessuna di queste assegnazioni, ma 0.0viene silenziosamente considerato come integrale 0. Questo è probabilmente un residuo di C, dove il letterale è 0stato trattato in modo speciale e potrebbe essenzialmente prendere qualsiasi tipo dato: intero, numero in virgola mobile, puntatore nullo ... lo chiami.


3
la domanda è perché ? Se vai a IL- sta spingendo il valore intero sullo stackIL_0001: ldc.i4.0
Ilya Ivanov

@IlyaIvanov Vedi aggiornamento. Ma ad essere onesti, la risposta è "nessuna buona ragione".
Konrad Rudolph

2
Penso che questo sia uno di quei casi in cui se guardi le specifiche C # , non è legale, ma se guardi qualsiasi compilatore C # prodotto da MS, lo fa.
Damien_The_Unbeliever

3

enum è davvero inteso (in tutte le lingue che lo supportano) come un modo per lavorare con stringhe significative e univoche (etichette) piuttosto che con valori numerici. Pertanto, nel tuo esempio, dovresti usare Bar e Baz solo quando hai a che fare con un tipo di dati enumerato Foo . Non dovresti mai usare (confrontare o assegnare) un numero intero, anche se molti compilatori ti permetteranno di farla franca (le enumerazioni sono solitamente numeri interi internamente), e in questo caso, uno 0,0 viene trattato con noncuranza come 0 dal compilatore.

Concettualmente, dovrebbe essere corretto aggiungere un intero n a un valore enumerato, per ottenere n valori più in basso sulla riga, o prendere val2 - val1 per vedere quanto sono distanti, ma a meno che la specifica del linguaggio non lo consenta esplicitamente, io Lo eviterei. (Pensa a un valore enumerato come se fosse come un puntatore C, nei modi in cui puoi usarlo.) Non c'è motivo per cui le enumerazioni non possano essere implementate con numeri in virgola mobile e un incremento fisso tra di loro, ma non ne ho sentito parlare questo viene fatto in qualsiasi lingua.


So che non dovrei usare le enumerazioni in questo modo in C #, ma ho trovato questo rompicapo e volevo sapere perché 0.0 funziona, ma 1.0 no. Sapevo che doveva essere qualcosa con il compilatore C # perché puoi vedere che il codice IL per Foo v1 = 0.0;è lo stesso di Foo v2 = Foo.Bar.
feO2x
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.