2010-09-02 13 views
13

Tengo curiosidad de por qué este código ...interruptor + enumeración = no todas las rutas de código devuelven un valor

enum Tile { Empty, White, Black }; 
    private string TileToString(Tile t) 
    { 
     switch (t) 
     { 
      case Tile.Empty: 
       return "."; 
      case Tile.White: 
       return "W"; 
      case Tile.Black: 
       return "B"; 
     } 
    } 

Lanza ese error. No es posible que t tome otro valor, ¿o sí? ¿No debería el compilador ser lo suficientemente inteligente como para darse cuenta de eso?

Respuesta

29

No, puede utilizar cualquier valor int convertido a Tile. Prueba esto:

Tile t = (Tile) 5; 
string s = TileToString(t); 

Una enumeración es un conjunto de nombres para los números, efectivamente ... pero ni el compilador ni el CLR hace cumplir que el valor del tipo de enumeración tiene un nombre. Es un dolor, pero ahí está ...

Sugeriría un caso por defecto que lanzó ArgumentException (o posiblemente ArgumentOutOfRangeException).

+4

Dado que el método es privada y por lo tanto la persona que llama debe estar pasando solamente cosas buenas (porque el autor de la persona que llama es * te *, no a sus usuarios) Sugeriría un Debug.Fail ("¡Mala persona que llama! ¡Sin galletas!") o similar en el caso predeterminado. –

+5

@Eric: Posiblemente ...excepto que aún necesita devolver algo o lanzar una excepción * también * para cumplir con los requisitos de accesibilidad. Personalmente, nunca he sido muy fanático de 'Debug. *' - Tiendo a gustarme ver las mismas excepciones en el modo de lanzamiento que en el modo de depuración, pero puede que solo sea yo. –

0
switch (t) 
{ 
    case Tile.Empty: 
     return "."; 
    case Tile.White: 
     return "W"; 
    case Tile.Black: 
     return "B"; 
    default: throw new NotSupportedException(); 
} 

Como señaló Jon, el valor es integral: una enumeración se puede convertir desde cualquier valor integral. Solo necesita manejar el valor predeterminado.

1

Esto se debe a que si su valor para t no coincide con ninguna de las cajas del interruptor, se caerá del interruptor y, por lo tanto, su método no devolverá un valor. Sin embargo, ha declarado que devolverá una cadena. Es necesario añadir un defecto en el interruptor, o un nulo de retorno:

enum Tile { Empty, White, Black }; 
    private string TileToString(Tile t) 
    { 
     switch (t) 
     { 
      case Tile.Empty: 
       return "."; 
      case Tile.White: 
       return "W"; 
      case Tile.Black: 
       return "B"; 
     } 
     return null; 
    } 
+0

"... si su valor para t no coincide con ninguna de las cajas de interruptores ..." - esa es exactamente mi pregunta. * ¿Cómo * no puede coincidir con ninguna de las cajas de interruptores? – mpen

+0

Porque, como dijo Jon, un Enum es en realidad solo una representación de un entero. Y dado que puede convertir un número entero en la enumeración de mosaico, técnicamente puede pasar un parámetro no válido. – BeRecursive

1

Añadir el caso por defecto:

enum Tile { Empty, White, Black }; 
    private string TileToString(Tile t) 
    { 
     switch (t) 
     { 
      case Tile.Empty: 
       return "."; 
      case Tile.White: 
       return "W"; 
      case Tile.Black: 
       return "B"; 
      default: 
       return "."; 
     } 
    } 
+0

Conozco la solución, estaba tratando de entender * por qué * esto es necesario. – mpen

+2

Una enumeración se puede extender con nuevos valores en cualquier momento. Por lo tanto, necesita el caso predeterminado para atraparlo, de lo contrario terminará con un código que no cubre todos los casos posibles. – Bart

22

Jon es, por supuesto, todo correcto que una enumeración puede tener cualquier valor de su subyacente tipo, y por lo tanto el cambio no es exhaustivo, y por lo tanto hay una ruta de código que no regresa. Sin embargo,, no es un análisis completo del problema. Incluso si fuera el caso de que el interruptor fuera exhaustivo, aún así obtendría el error.

Inténtelo:

int M(bool b) 
{ 
    switch(b) 
    { 
     case true : return 123; 
     case false: return 456; 
    } 
} 

O

int M(byte b) 
{ 
    switch(b) 
    { 
     case 0: case 1: case 2: ... all of them ... case 255: return 123; 
    } 
} 

En ambos casos se obtendrá el mismo "punto final alcanzable en el método no-vacío" de error.

Esto es simplemente un descuido en la sección "comprobación de alcanzabilidad" de la especificación C#. Definimos el punto final de una instrucción switch como alcanzable si no tiene una sección predeterminada, punto. No hay dispensa especial para los interruptores que consumen de forma exhaustiva todos los valores posibles de su entrada. Es un caso de esquina que los diseñadores de idiomas se perdieron, y nunca ha sido una prioridad lo suficientemente alta como para solucionarlo.

Para otros tres hechos interesantes sobre el análisis de los estados de conmutación, véase:

http://ericlippert.com/2009/08/13/four-switch-oddities/

+0

Aw diablos, quiero darte ambos cheques ahora. Jaja ... eso es justo. ¡Pensé que esta era la verdadera razón! – mpen

Cuestiones relacionadas