Estaba escribiendo este código:
private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
switch (criterion.ChangeAction)
{
case BindingType.Inherited:
var action = (byte)ChangeAction.Inherit;
return (x => x.Action == action);
case BindingType.ExplicitValue:
var action = (byte)ChangeAction.SetValue;
return (x => x.Action == action);
default:
// TODO: Localize errors
throw new InvalidOperationException("Invalid criterion.");
}
}
Y me sorprendió encontrar un error de compilación:
Una variable local llamada 'acción' ya está definida en este ámbito
Era un problema bastante fácil de resolver; solo deshacerme del segundo varhizo el truco.
Evidentemente, las variables declaradas en casebloques tienen el alcance del padre switch, pero tengo curiosidad de por qué esto es así. Dado que C # no permite la ejecución de caer a través de otros casos (que requiere break , return, throw, o goto casedeclaraciones al final de cada casebloque), parece bastante extraño que permitiría a las declaraciones de variables dentro de una casea usarse o conflicto con las variables en cualquier otro case. En otras palabras, las variables parecen caer a través de las casedeclaraciones aunque la ejecución no puede. C # se esfuerza mucho para promover la legibilidad al prohibir algunas construcciones de otros lenguajes que son confusos o de abuso fácil. Pero esto parece que está destinado a causar confusión. Considere los siguientes escenarios:
Si fuera a cambiarlo a esto:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);Obtengo " Uso de la variable local no asignada 'acción' ". Esto es confuso porque en cualquier otra construcción en C # que se me ocurra
var action = ...inicializaría la variable, pero aquí simplemente la declara.Si tuviera que cambiar los casos de esta manera:
case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; return (x => x.Action == action); case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action);Obtengo " No se puede usar la variable local 'acción' antes de que se declare ". Entonces, el orden de los bloques de casos parece ser importante aquí de una manera que no es del todo obvia: normalmente podría escribirlos en el orden que desee, pero debido a que
vardebe aparecer en el primer bloque dondeactionse usa, tengo que ajustar loscasebloques en consecuencia.Si fuera a cambiarlo a esto:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;Entonces no obtengo ningún error, pero en cierto sentido, parece que a la variable se le está asignando un valor antes de que se declare.
(Aunque no puedo pensar en ningún momento en el que realmente quieras hacer esto, ni siquiera sabía quegoto caseexistía antes de hoy)
Entonces mi pregunta es, ¿por qué los diseñadores de C # no le dieron a los casebloques su propio alcance local? ¿Hay alguna razón histórica o técnica para esto?
fuente

actionvariable antes de laswitchdeclaración, o ponga cada caso en sus propios corchetes, y obtendrá un comportamiento sensato.switchsolo tengo curiosidad sobre el razonamiento detrás de este diseño.breakno sea posible en C #.Respuestas:
Creo que una buena razón es que en cualquier otro caso, el alcance de una variable local "normal" es un bloque delimitado por llaves (
{}). Las variables locales que no son normales aparecen en una construcción especial antes de una declaración (que generalmente es un bloque), como unaforvariable de bucle o una variable declarada enusing.Una excepción más son las variables locales en las expresiones de consulta LINQ, pero son completamente diferentes de las declaraciones de variables locales normales, por lo que no creo que haya una posibilidad de confusión allí.
Como referencia, las reglas están en §3.7 Ámbitos de la especificación C #:
(Aunque no estoy completamente seguro de por qué se
switchmenciona explícitamente el bloque, ya que no tiene ninguna sintaxis especial para las declaraciones de variables locales, a diferencia de todas las otras construcciones mencionadas).fuente
switchesparece que me comporto de manera idéntica a este respecto (excepto por requerir saltos al final decase's). Parece que este comportamiento simplemente se copió desde allí. Entonces, supongo que la respuesta corta es: lascasedeclaraciones no crean bloques, simplemente definen las divisiones de unswitchbloque y, por lo tanto, no tienen ámbitos por sí mismos.Pero lo hace. Puede crear ámbitos locales en cualquier lugar ajustando líneas con
{}fuente
Citaré a Eric Lippert, cuya respuesta es bastante clara sobre el tema:
Por lo tanto, a menos que sea más íntimo con el equipo de desarrolladores de C # de 1999 que Eric Lippert, ¡nunca sabrá la razón exacta!
fuente
La explicación es simple: es así en C. Los lenguajes como C ++, Java y C # han copiado la sintaxis y el alcance de la instrucción switch por razones de familiaridad.
(Como se indicó en otra respuesta, los desarrolladores de C # no tienen documentación sobre cómo se tomó esta decisión con respecto al alcance de los casos. Pero el principio no declarado de la sintaxis de C # era que, a menos que tuvieran razones convincentes para hacerlo de manera diferente, copiaban Java. )
En C, las declaraciones de caso son similares a las etiquetas de goto. La declaración de cambio es realmente una sintaxis más agradable para un goto calculado . Los casos definen los puntos de entrada en el bloque de interruptores. Por defecto, el resto del código se ejecutará, a menos que haya una salida explícita. Por lo tanto, solo tiene sentido que usen el mismo alcance.
(Más fundamentalmente. Los Goto no están estructurados: no definen ni delimitan secciones de código, solo definen puntos de salto. Por lo tanto, una etiqueta Goto no puede introducir un alcance).
C # retiene la sintaxis, pero introduce una protección contra el "fallo" al requerir una salida después de cada cláusula de caso (no vacía). ¡Pero esto cambia la forma en que pensamos en el cambio! Los casos ahora son ramas alternas , como las ramas en un if-else. Esto significa que esperaríamos que cada rama defina su propio alcance al igual que las cláusulas if o las cláusulas de iteración.
En resumen: los casos comparten el mismo alcance porque así es como está en C. Pero parece extraño e inconsistente en C # porque pensamos en los casos como ramas alternativas en lugar de ir a objetivos.
fuente
Una forma simplificada de ver el alcance es considerar el alcance por bloques
{}.Como
switchno contiene ningún bloque, no puede tener ámbitos diferentes.fuente
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */? No hay bloques, pero hay diferentes ámbitos.fordeclaración.switchno crea bloques para cada caso, solo en el nivel superior. La similitud es que cada declaración crea un bloque (contandoswitchcomo la declaración).caselugar?caseno contiene un bloque, a menos que decidas agregar uno opcionalmente.fordura la siguiente declaración a menos que agregue,{}por lo que siempre tiene un bloque, perocasedura hasta que algo haga que abandone el bloque real, laswitchdeclaración.