switch - return enum c#
¿Por qué se permiten las operaciones entre diferentes tipos de enumeración en otra declaración de enumeración pero no en otra parte? (4)
El compilador de C # permite operaciones entre diferentes tipos de enumeración en otra declaración de tipo de enumeración, como esto:
public enum VerticalAnchors
{
Top=1,
Mid=2,
Bot=4
}
public enum HorizontalAnchors
{
Lef=8,
Mid=16,
Rig=32
}
public enum VisualAnchors
{
TopLef = VerticalAnchors.Top | HorizontalAnchors.Lef,
TopMid = VerticalAnchors.Top | HorizontalAnchors.Mid,
TopRig = VerticalAnchors.Top | HorizontalAnchors.Rig,
MidLef = VerticalAnchors.Mid | HorizontalAnchors.Lef,
MidMid = VerticalAnchors.Mid | HorizontalAnchors.Mid,
MidRig = VerticalAnchors.Mid | HorizontalAnchors.Rig,
BotLef = VerticalAnchors.Bot | HorizontalAnchors.Lef,
BotMid = VerticalAnchors.Bot | HorizontalAnchors.Mid,
BotRig = VerticalAnchors.Bot | HorizontalAnchors.Rig
}
pero los prohíbe dentro del código de método, es decir, la operación:
VerticalAnchors.Top | HorizontalAnchors.Lef;
Se marca con este error:
Operador ''|'' no se puede aplicar a los operandos de tipo ''VerticalAnchors'' y ''HorizontalAnchors''.
Hay una solución, por supuesto:
(int)VerticalAnchors.Top | (int)HorizontalAnchors.Lef
Tengo curiosidad por este comportamiento del compilador. ¿Por qué se permiten las operaciones entre diferentes tipos de enumeración en otra declaración de enumeración pero no en otra parte?
C # permite que la definición de valores de enumeración contenga una expresión de valor constante, de modo que un valor de enumeración puede ser una combinación de enumeraciones, por ejemplo, [Flags]
. El compilador evalúa cada valor de enumeración en la expresión como int
(generalmente), por lo tanto, puede realizar operaciones de bit a bit y aritméticas en el valor de enumeración.
Fuera de una definición de enumeración, debe convertir la enumeración a un tipo primitivo antes de realizar una operación en ella.
Como no hizo una pregunta en su pregunta, pretenderé que hizo algunas preguntas interesantes y las responderé:
¿Es cierto que dentro de una declaración de enumeración, puede usar valores de otras enumeraciones en los inicializadores?
Sí. Puedes decir
enum Fruit { Apple }
enum Animal { Giraffe = Fruit.Apple }
Aunque no sería legal asignar Fruit.Apple
a una variable de tipo Animal
sin reparto.
Este hecho es ocasionalmente sorprendente. De hecho, yo mismo me sorprendí. Cuando intenté hacer eso por primera vez, para probar parte del compilador, pensé que era un posible error.
¿Dónde en la especificación dice que es legal?
La sección 14.3 dice que el inicializador debe ser una constante y que la constante se convertirá al tipo subyacente de la enumeración. Los miembros de la enumeración son constantes.
Ah, pero ¿qué pasa con este caso?
enum Fruit { Apple = 1 }
enum Shape { Square = 2 }
enum Animal { Giraffe = Fruit.Apple | Shape.Square }
Esa expresión no es una expresión constante legal, en primer lugar, ¿qué pasa?
Ok me tienes ahi La sección 14.3 también dice que los miembros de la enumeración utilizados en los inicializadores no necesitan ser lanzados, pero no está claro si significa que los miembros de la enumeración se están inicializando o los miembros de cualquier enumeración . O bien es una interpretación razonable, pero sin un lenguaje específico, es fácil pensar que el significado original es el que se pretende.
Este es, pues, un defecto conocido; Se lo señalé a Mads hace unos años y nunca se ha resuelto. Por un lado, la especificación no lo permite claramente. Por otro lado, el comportamiento es a la vez útil y en el espíritu, si no completamente en la letra, de la especificación.
Básicamente, lo que hace la implementación es cuando está procesando un inicializador de enumeración, trata a todos los miembros de la enumeración como expresiones constantes de su tipo subyacente. (Por supuesto, debe asegurarse de que las definiciones de enumeración sean acíclicas, pero tal vez sea mejor dejarlas para otra pregunta). Por lo tanto, no "ve" que la Fruit
y la Shape
no tienen un operador definido por "o".
Aunque la redacción de especificaciones no está clara, esta es una característica deseable. De hecho, lo usé frecuentemente en el equipo de Roslyn:
[Flags] enum UnaryOperatorKind {
Integer = 0x0001,
...
UnaryMinus = 0x0100,
...
IntegerMinus = Integer | UnaryMinus
... }
[Flags] enum BinaryOperatorKind {
...
IntegerAddition = UnaryOperatorKind.Integer | Addition
... }
Es muy útil poder mezclar y combinar banderas tomadas de varias enumeraciones.
En realidad no está en la spec por lo que puedo decir. Hay algo relacionado:
Si la declaración del miembro de enumeración tiene un inicializador de expresión constante, el valor de esa expresión constante, convertida implícitamente al tipo subyacente de la enumeración, es el valor asociado del miembro de enumeración.
Aunque VerticalAnchors.Top & HorizontalAnchors.Lef
tiene el tipo VerticalAnchors
se puede convertir implícitamente a VisualAnchors
. Pero esto no explica por qué la expresión constante en sí misma admite conversiones implícitas en todas partes.
En realidad, parece estar explícitamente en contra de la especificación:
La evaluación en tiempo de compilación de expresiones constantes usa las mismas reglas que la evaluación en tiempo de ejecución de expresiones no constantes, excepto que cuando la evaluación en tiempo de ejecución hubiera generado una excepción, la evaluación en tiempo de compilación provoca un error en tiempo de compilación.
Si no me perdí algo, la especificación no solo no permite esto de manera explícita, sino que no lo permite. Bajo ese supuesto sería un error de compilación.
Interesante. También puedes preguntar por qué esto está permitido:
enum MyType
{
Member = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase,
}
cuando esto no está permitido:
var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase;
La razón parece ser que dentro de la declaración de una enumeración, en los inicializadores de miembros de enumeración, cualquier valor de enumeración, incluso un valor de una enumeración no relacionada, se considera que se convierte en su tipo subyacente. Entonces el compilador ve el ejemplo anterior como:
enum MyType
{
Member = (int)(DayOfWeek.Thursday) | (int)(StringComparison.CurrentCultureIgnoreCase),
}
Encuentro esto muy extraño. Sabía que podía usar los valores de la misma enumeración directamente (sin indicar la conversión al tipo subyacente), como en la última línea de:
enum SomeType
{
Read = 1,
Write = 2,
ReadWrite = Read | Write,
}
pero me parece muy sorprendente que los miembros de otras enumeraciones también se conviertan a sus tipos de enteros subyacentes.