Solo por curiosidad: ¿por qué puedo asignar 0.0 a una variable que es de tipo enumeración, pero no 1.0? Eche un vistazo al siguiente código:
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
}
}
Pensé que las conversiones entre tipos numéricos y valores de enumeración solo se permiten a través de conversiones. Es decir, podría escribir Foo value2 = (Foo) 1.0;
para que la línea 2 de Main
pueda compilarse. ¿Por qué hay una excepción para el valor 0.0
en C #?
1.0
literal a una enumeración personalizada.0
lugar. Una vez tuve una pregunta similar y Rawling publicó una gran respuesta aquí .Respuestas:
Es un error que puedes usar 0.0. El compilador trata implícitamente todas las expresiones constantes con un valor de cero como solo 0.
Ahora, es correcto que el compilador permita una conversión implícita de una
int
expresión constante de 0 a su enumeración según la sección 6.1.3 de la especificación C # 5:Hablé con el equipo de C # sobre esto antes: les hubiera gustado haber eliminado la conversión accidental de 0.0 (y de hecho 0.0my 0.0f) a los valores de enumeración, pero desafortunadamente deduzco que rompió demasiado código, aunque en primer lugar, nunca debería haberse permitido.
El Mono
mcs
compilador prohíbe todas estas conversiones de punto flotante, aunque no permite:const int Zero = 0; ... SomeEnum x = Zero;
a pesar de que
Zero
es una expresión constante pero no un decimal-entero-literal.No me sorprendería ver que la especificación de C # cambie en el futuro para permitir cualquier expresión constante entera con un valor de 0 (es decir, para imitar
mcs
), pero no esperaría que las conversiones de punto flotante fueran oficialmente correctas. (Me he equivocado antes al predecir el futuro de C #, por supuesto ...)fuente
1-1
unaint
expresión constante con un valor de0
. Pero como puede observar, el compilador está fuera de línea con la especificación aquí.it broke too much code
- Es realmente difícil imaginar alguna razón para escribir tal código.SomeEnum x = (SomeEnum) 0;
. Ese es el caso, ya sea que haya un valor cero con nombre o no.Test.Foo
es 1, no 0 ... de nuevo, eso es exactamente lo mismo que si escribieraTest v1 = (Test) 0;
, y ese comportamiento es válido para cualquier valor que no sea un valor con nombre en la enumeración.La respuesta de Jon es correcta. Le agregaría los siguientes puntos.
Yo causé este error tonto y vergonzoso. Muchas disculpas.
El error se debió a que entendí mal la semántica de un predicado "la expresión es cero" en el compilador; Creí que solo estaba comprobando la igualdad de enteros cero, cuando de hecho estaba comprobando más cosas como "¿es este el valor predeterminado de este tipo?" De hecho, en una versión anterior del error, ¡era posible asignar el valor predeterminado de cualquier tipo a una enumeración! Ahora son solo valores predeterminados de números. (Lección: nombre sus predicados auxiliares con cuidado).
El comportamiento que estaba intentando implementar y que arruiné fue de hecho una solución para un error ligeramente diferente. Puede leer toda la terrible historia aquí: https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-one y https://docs.microsoft .com / en-us / archive / blogs / ericlippert / the-root-of-all-evil-part-two (Lección: Es muy fácil introducir nuevos errores peores mientras se corrigen los antiguos).
El equipo de C # decidió consagrar este comportamiento defectuoso en lugar de corregirlo porque el riesgo de romper el código existente sin un beneficio convincente era demasiado alto. (Lección: ¡hazlo bien la primera vez!)
El código que escribí en Roslyn para preservar este comportamiento se puede encontrar en el método
IsConstantNumericZero
en https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs - Véalo para obtener más detalles sobre cuál es exactamente el comportamiento de Roslyn. Escribí casi todo el código en el directorio de conversiones; Te animo a que lo leas todo, ya que hay muchos datos interesantes sobre cómo C # difiere de la especificación en los comentarios. Decoré cada uno con SPEC VIOLATION para que sean fáciles de encontrar.Un punto más de interés: C # también permite que se use cualquier valor de enumeración en un inicializador de enumeración independientemente de su zeroness:
enum E { A = 1 } enum F { B = E.A } // ???
La especificación es algo vaga en cuanto a si esto debería ser legal o no, pero nuevamente, como ha estado en el compilador durante mucho tiempo, es probable que los nuevos compiladores mantengan el comportamiento.
fuente
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.
No creo que haya muchas personas que dependan de este comportamiento, y es una de estas rarezas que quizás haya sido mejor arreglar. Sin embargo, tampoco hace mucho daño (a excepción de los proyectos que implementan la especificación).Las enumeraciones en C # son, por definición, valores integrales. Por coherencia, C # no debería aceptar ninguna de estas asignaciones, pero
0.0
se trata silenciosamente como integral0
. Esto es probablemente un vestigio de C, donde el literal0
fue tratado especialmente y esencialmente podría tomar cualquier tipo dado: entero, número de punto flotante, puntero nulo… lo que sea.fuente
IL
, está empujando el valor entero en la pilaIL_0001: ldc.i4.0
Enum está realmente destinado (en todos los lenguajes que lo admiten) a ser una forma de trabajar con cadenas (etiquetas) significativas y únicas en lugar de valores numéricos. Por lo tanto, en su ejemplo, solo debería usar Bar y Baz cuando se trata de un tipo de datos enumerado Foo . Nunca debe usar (comparar o asignar) un número entero, aunque muchos compiladores le permitirán salirse con la suya (las enumeraciones suelen ser números enteros internamente) y, en este caso, el compilador trata descuidadamente un 0.0 como un 0.
Conceptualmente, debería estar bien agregar un número entero n a un valor enumerado, para obtener n valores más abajo en la línea, o tomar val2 - val1 para ver qué tan separados están, pero a menos que la especificación del lenguaje lo permita explícitamente, I Lo evitaría. (Piense en un valor enumerado como un puntero C, en las formas en que puede usarlo). No hay razón para que las enumeraciones no se puedan implementar con números de punto flotante y un incremento fijo entre ellos, pero no he oído hablar de esto se hace en cualquier idioma.
fuente
Foo v1 = 0.0;
es el mismo que paraFoo v2 = Foo.Bar
.