¿Por qué fue la declaración (j ++); ¿prohibido?

169

El siguiente código es incorrecto (verlo en ideone ):

public class Test
{
    public static void Main()
    {
        int j = 5;
        (j++);      // if we remove the "(" and ")" then this compiles fine.
    }
}

error CS0201: solo las asignaciones, llamadas, incrementos, decrementos, esperas y nuevas expresiones de objetos se pueden usar como una declaración

  1. ¿Por qué se compila el código cuando eliminamos los paréntesis?
  2. ¿Por qué no se compila con los paréntesis?
  3. ¿Por qué se diseñó C # de esa manera?
usuario10607
fuente
29
@Servy, muchas personas involucradas con el diseño del lenguaje C # están en SO, por eso pregunté. Algo mal con esto?
user10607
34
No puedo imaginar una pregunta más sobre el tema que "¿por qué un lenguaje de programación específico maneja este comportamiento específico de esta manera?"
Mage Xy
20
Ahora tenemos una respuesta de Eric Lippert. Tal vez desee repensar su decisión sobre la respuesta aceptada. Es Navidad, así que quizás hayas aceptado la respuesta demasiado pronto.
Thomas Weller
17
@Servy ¿Estás insinuando que las decisiones de diseño nunca se documentan y son absolutamente desconocidas para todos los demás? Él no pedir a los usuarios de SO de adivinar, que está pidiendo una respuesta - eso no implica que está pidiendo una conjetura. Que la gente responda con una suposición depende de ellos , no de él. Y como OP señaló, no son gente de desbordamiento de pila que hicieron realidad el trabajo en C # y tomado estas decisiones.
Rob
55
@Rob: El problema es que sí, a menos que la justificación ya esté documentada en alguna parte, debe hacerlo, pero la especulación es divertida y ¿quién no quiere compartir la suya? Si encuentra una manera de asegurarse de que tales conjeturas puras (o "educadas") sean descartadas de manera confiable, dígame y haremos una fortuna.
Deduplicador

Respuestas:

221

Conocimientos profundos apreciados.

Haré lo mejor que pueda.

Como han señalado otras respuestas, lo que está sucediendo aquí es que el compilador está detectando que una expresión se está utilizando como una declaración . En muchos lenguajes, C, JavaScript y muchos otros, es perfectamente legal usar una expresión como una declaración. 2 + 2;es legal en estos idiomas, a pesar de que esta es una declaración que no tiene ningún efecto. Algunas expresiones son útiles solo por sus valores, algunas expresiones son útiles solo por sus efectos secundarios (como una llamada a un método de retorno nulo) y algunas expresiones, desafortunadamente, son útiles para ambos. (Como incremento).

El punto es: las declaraciones que consisten solo en expresiones son casi con certeza errores a menos que esas expresiones se consideren más útiles para sus efectos secundarios que sus valores . Los diseñadores de C # deseaban encontrar un punto medio, permitiendo expresiones que generalmente se consideraban como efectos secundarios, mientras que no permitían aquellas que también se consideran útiles para sus valores. El conjunto de expresiones que identificaron en C # 1.0 fueron incrementos, decrementos, llamadas a métodos, asignaciones y, de forma algo controvertida, invocaciones de constructores.


A UN LADO: Uno normalmente piensa que la construcción de un objeto se usa por el valor que produce, no por el efecto secundario de la construcción; en mi opinión, permitir new Foo();es un poco un error. En particular, he visto este patrón en el código del mundo real que causó un defecto de seguridad:

catch(FooException ex) { new BarException(ex); } 

Puede ser sorprendentemente difícil detectar este defecto si el código es complicado.


Por lo tanto, el compilador funciona para detectar todas las declaraciones que consisten en expresiones que no están en esa lista. En particular, las expresiones entre paréntesis se identifican solo como eso: expresiones entre paréntesis. No están en la lista de "permitidas como expresiones de declaración", por lo que no están permitidas.

Todo esto está al servicio de un principio de diseño del lenguaje C #. Si escribiste (x++);, probablemente estabas haciendo algo mal . Esto es probablemente un error tipográfico M(x++);o algo justo. Recuerde, la actitud del equipo compilador de C # no es " ¿podemos encontrar alguna forma de hacer que esto funcione? " La actitud del equipo compilador de C # es " si el código plausible parece un posible error, informemos al desarrollador ". A los desarrolladores de C # les gusta esa actitud.

Ahora, todo lo que dijo, en realidad hay unos pocos casos raros en que la especificación C # no implica ni del estado de plano que los paréntesis no están permitidos, pero el compilador de C # les permite de todos modos. En casi todos esos casos, la discrepancia menor entre el comportamiento especificado y el comportamiento permitido es completamente inofensiva, por lo que los escritores del compilador nunca han solucionado estos pequeños errores. Puedes leer sobre eso aquí:

¿Hay alguna diferencia entre return myVar y return (myVar)?

Eric Lippert
fuente
55
Re: "Uno normalmente piensa que una invocación de constructor se usa por el valor que produce, no por el efecto secundario de la construcción; en mi opinión, esto es un error": me imagino que ese es un factor en esta decisión era que si el compilador lo prohibía, no habría ninguna solución limpia en los casos en que lo llamara por un efecto secundario. (La mayoría de las otras declaraciones de expresiones prohibidas tienen partes extrañas que no tienen ningún propósito y pueden eliminarse, con la excepción de ... ? ... : ..., dónde se debe usar la solución if/ en su elselugar.)
ruakh
1
@ruakh: Dado que este es un comportamiento que debe desalentarse, no es necesaria una solución limpia, siempre que haya una solución razonablemente barata / simple. Que hay; asígnelo a una variable y no use esa variable. Si quieres ser descarado de que no lo estás usando, dale a esa línea de código su propio alcance. Si bien técnicamente se podría argumentar que esto cambia la semántica del código (una asignación de referencia inútil sigue siendo una asignación de referencia inútil), en la práctica es poco probable que importe. Admito que genera IL ligeramente diferente ... pero solo si se compila sin optimizaciones.
Brian
Safari, Firefox y Chrome todos dan ReferenceError al escribir contineu;.
Brian McCutchon el
2
@McBrainy: Tienes razón. Estoy recordando mal el defecto que resulta de la falta de ortografía; Revisaré mis notas. Mientras tanto, he eliminado la declaración ofensiva ya que no es pertinente para el punto más amplio aquí.
Eric Lippert
1
@ Mike: estoy de acuerdo. Otra situación en la que a veces vemos la declaración son los casos de prueba, try { new Foo(null); } catch (ArgumentNullException)...pero obviamente estas situaciones son, por definición, no un código de producción. Parece razonable que dicho código se pueda escribir para asignar a una variable ficticia.
Eric Lippert el
46

En la especificación del lenguaje C #

Las declaraciones de expresión se utilizan para evaluar expresiones. Las expresiones que pueden usarse como enunciados incluyen invocaciones de métodos, asignaciones de objetos usando el nuevo operador, asignaciones usando = y los operadores de asignación compuesta, operaciones de incremento y decremento usando los operadores ++ y - y esperan expresiones.

Poner paréntesis alrededor de una declaración crea una nueva llamada expresión entre paréntesis. De la especificación:

Una expresión entre paréntesis consiste en una expresión encerrada entre paréntesis. ... Una expresión entre paréntesis se evalúa evaluando la expresión entre paréntesis. Si la expresión entre paréntesis denota un espacio de nombres o tipo, se produce un error en tiempo de compilación. De lo contrario, el resultado de la expresión entre paréntesis es el resultado de la evaluación de la expresión contenida.

Dado que las expresiones entre paréntesis no se enumeran como una declaración de expresión válida, no es una declaración válida según la especificación. Nadie sabe con certeza por qué los diseñadores decidieron hacerlo de esta manera, pero mi apuesta es porque los paréntesis no son útiles si la declaración completa está entre paréntesis: stmty (stmt)son exactamente lo mismo.

Frank Bryce
fuente
44
@Servy: Si lees atentamente, fue un poco más matizado que eso. La llamada "Declaración de expresión" es de hecho una declaración válida, y la especificación enumera los tipos de expresiones que pueden ser declaraciones válidas. Sin embargo, repito de la especificación que el tipo de expresión llamada "expresión entre paréntesis" NO está en la lista de expresiones válidas que se pueden usar como declaraciones de expresiones válidas.
Frank Bryce
44
El OPno pregunta nada sobre el diseño del lenguaje. Solo quiere saber por qué esto es un error. La respuesta es: porque no es una declaración válida .
Leandro
9
OK, mi mal. La respuesta es: porque, según la especificación , no es una declaración válida. Ahí lo tienes: ¡una razón de diseño !
Leandro
3
La pregunta no es preguntar por una razón profunda, basada en el diseño, de por qué el lenguaje fue diseñado para manejar esta sintaxis de esta manera: está preguntando por qué una pieza de código se compila cuando otra pieza de código (que para los principiantes parece que debería comportarse) idénticamente) no se compila. Esta publicación responde eso.
Mage Xy
44
@Servy JohnCarpenter no solo dice "No puedes hacer eso". Él dice que esas dos cosas por las que te confundiste no son lo mismo. j ++ es una declaración de expresión, mientras que (j ++) es una expresión entre paréntesis. De repente, el OP ahora sabe la diferencia y cuáles son. Esa es una buena respuesta. Responde el cuerpo de su pregunta, no el título. Creo que mucha de la controversia proviene de la palabra "diseño" en el título de la pregunta, pero esto no tiene que ser respondido por los diseñadores, solo extraído de las especificaciones.
thinklarge
19

porque los corchetes alrededor del i++ están creando / definiendo una expresión ... como dice el mensaje de error ... una expresión simple no puede usarse como una declaración.

¿Por qué el lenguaje fue diseñado para ser así? para evitar errores, que tienen expresiones engañosas como declaraciones, que no producen ningún efecto secundario como tener el código

int j = 5;
j+1; 

la segunda línea no tiene efecto (pero puede que no lo hayas notado). Pero en lugar de que el compilador lo elimine (porque el código no es necesario). Explícitamente le pide que lo elimine (para que sepa el error) O lo arregle en caso de que haya olvidado escribir algo.

editar :

para aclarar más la parte sobre el frenado ... los corchetes en c # (además de otros usos, como la conversión y la llamada a funciones), se usan para agrupar expresiones y devolver una sola expresión (marca de las subexpresiones).

en ese nivel de código solo se permiten declaraciones ...

j++; is a valid statement because it produces side effects

pero al usar el freno lo estás convirtiendo en una expresión

myTempExpression = (j++)

y esto

myTempExpression;

no es válido porque el compilador no puede asegurar que la expresión sea un efecto secundario (no sin incurrir en el problema de detención).

CaldasGSM
fuente
Sí, eso fue lo que pensé al principio también. Pero esto lo hace tener un efecto secundario. Realiza post-incremento de jvariable ¿verdad?
user10607
1
j++;es una declaración válida, pero al usar los corchetes estás diciendo let me take this stement and turn it into an expression... y las expresiones no son válidas en ese punto del código
CaldasGSM
Es una pena que no haya una forma de declaración "calcular e ignorar", ya que hay ocasiones en que el código puede desear afirmar que se puede calcular un valor sin lanzar una excepción, pero no importa el valor real así calculado. Una declaración de calcular e ignorar no se usaría con demasiada frecuencia, pero dejaría clara la intención del programador en tales casos.
supercat