Por que a declaração (j ++); proibido?

169

O código a seguir está errado (veja no ideone ):

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

erro CS0201: Apenas designação, chamada, incremento, decremento, espera e novas expressões de objeto podem ser usadas como uma instrução

  1. Por que o código é compilado quando removemos os parênteses?
  2. Por que não compila com parênteses?
  3. Por que o C # foi projetado dessa maneira?
user10607
fonte
29
@ Muitas pessoas envolvidas com o design da linguagem C # estão no SO, por isso perguntei. Algo errado com isso?
user10607
34
Não consigo imaginar uma pergunta mais específica do que "por que uma linguagem de programação específica lida com esse comportamento específico dessa maneira?"
precisa
20
Agora temos uma resposta de Eric Lippert. Talvez você queira repensar sua decisão sobre a resposta aceita. É Natal, então talvez você tenha aceitado a resposta muito cedo.
22615 Thomas Weller
17
@Servy Você está sugerindo que as decisões de design nunca são documentadas e são absolutamente desconhecidas para todos os outros? Ele não está pedindo que os usuários adivinhem, ele está pedindo uma resposta - isso não implica que ele esteja pedindo um palpite. Se as pessoas respondem com um palpite é sobre eles , não ele. E como OP apontou, não são as pessoas sobre estouro de pilha que fizeram realmente o trabalho em C # e fizeram essas decisões.
Rob
5
@ Rob: O problema é que sim, a menos que a lógica já esteja documentada em algum lugar, deve, mas a especulação é divertida e quem não quer compartilhar a sua? Se você encontrar uma maneira de garantir que essas suposições puras (ou "educadas") sejam descartadas com segurança, diga-me e faremos uma fortuna.
Deduplicator

Respostas:

221

Apreciação profunda.

Eu farei o meu melhor.

Como outras respostas observaram, o que está acontecendo aqui é que o compilador está detectando que uma expressão está sendo usada como uma declaração . Em muitos idiomas - C, JavaScript e muitos outros - é perfeitamente legal usar uma expressão como uma declaração. 2 + 2;é legal nesses idiomas, mesmo que essa seja uma declaração que não tem efeito. Algumas expressões são úteis apenas para seus valores, algumas expressões são úteis apenas para seus efeitos colaterais (como uma chamada para um método de retorno nulo) e algumas expressões, infelizmente, são úteis para ambos. (Como incremento.)

A questão é: declarações que consistem apenas em expressões são quase certamente erros, a menos que essas expressões sejam consideradas mais úteis para seus efeitos colaterais do que seus valores . Os designers de C # desejavam encontrar um meio termo, permitindo expressões que geralmente eram consideradas como efeito colateral, enquanto não permitiam aquelas que também são geralmente consideradas úteis para seus valores. O conjunto de expressões que eles identificaram no C # 1.0 foram incrementos, decréscimos, chamadas de método, atribuições e, de certa forma controversa, invocações de construtores.


À parte: Normalmente, considera-se uma construção de objeto como sendo usada pelo valor que produz, não pelo efeito colateral da construção; na minha opinião, permitir new Foo();é um pouco incorreto. Em particular, eu vi esse padrão no código do mundo real que causou um defeito de segurança:

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

Pode ser surpreendentemente difícil detectar esse defeito se o código for complicado.


O compilador, portanto, trabalha para detectar todas as instruções que consistem em expressões que não estão nessa lista. Em particular, expressões entre parênteses são identificadas como exatamente isso - expressões entre parênteses. Eles não estão na lista de "permitido como expressões de instrução", portanto, não são permitidos.

Tudo isso está em serviço de um princípio de design da linguagem C #. Se você digitou (x++);, provavelmente estava fazendo algo errado . Este é provavelmente um erro de digitação M(x++);ou algo justo. Lembre-se, a atitude da equipe do compilador C # não é " podemos descobrir uma maneira de fazer isso funcionar? " A atitude da equipe do compilador C # é " se um código plausível parecer um provável erro, vamos informar o desenvolvedor ". Desenvolvedores de C # gostam dessa atitude.

Agora, tudo o que disse, realmente existem alguns casos estranhos onde o # especificação C não implicam ou estaduais abertamente que parênteses são permitidos, mas o compilador C # permite que eles de qualquer maneira. Em quase todos esses casos, a menor discrepância entre o comportamento especificado e o permitido é totalmente inofensiva, portanto, os escritores do compilador nunca corrigiram esses pequenos bugs. Você pode ler sobre aqueles aqui:

Existe uma diferença entre return myVar vs. return (myVar)?

Eric Lippert
fonte
5
Re: "Normalmente se pensa em uma invocação de construtor como sendo usada pelo valor que ela produz, não pelo efeito colateral da construção; na minha opinião, isso é um pouco incorreto": eu imagino que um fator nessa decisão era que, se o compilador proibiu, não haveria qualquer correção limpa nos casos em que você está chamando-o para um efeito colateral. (A maioria das outras demonstrações expressão proibidas têm partes estranhas que servem nenhum propósito e podem ser removidos, com a excepção de ... ? ... : ..., em que a solução é para usar if/ elseem vez disso.)
ruach
1
@ruakh: Como esse é um comportamento que deve ser desencorajado, uma correção limpa não é necessária, desde que exista uma correção razoavelmente barata / simples. Qual existe; atribua-o a uma variável e não use essa variável. Se você quiser ser flagrante por não estar usando, dê a essa linha de código seu próprio escopo. Embora tecnicamente alguém possa argumentar que isso altera a semântica do código (uma atribuição de referência inútil ainda é uma atribuição de referência inútil), na prática é improvável que isso importe. Admito que ele gera IL ligeiramente diferente ... mas apenas se compilado sem otimizações.
23715 Brian
Safari, Firefox e Chrome fornecem ReferenceError ao digitar contineu;.
Brian McCutchon
2
@McBrainy: Você está certo. Estou relembrando o defeito que resulta do erro de ortografia; Vou verificar minhas anotações. Enquanto isso, excluí a declaração ofensiva, pois não é pertinente ao ponto mais amplo aqui.
Eric Lippert
1
@ Mike: eu concordo. Outra situação em que às vezes vemos a declaração é como casos de teste, try { new Foo(null); } catch (ArgumentNullException)...mas obviamente essas situações são, por definição, não código de produção. Parece razoável que esse código possa ser escrito para atribuir a uma variável dummy.
precisa saber é o seguinte
46

Na especificação da linguagem C #

Instruções de expressão são usadas para avaliar expressões. Expressões que podem ser usadas como instruções incluem invocações de métodos, alocações de objetos usando o novo operador, atribuições usando = e operadores de atribuição composta, operações de incremento e decremento usando os operadores ++ e - e aguardam expressões.

Colocar parênteses em torno de uma instrução cria uma nova expressão chamada parênteses. A partir da especificação:

Uma expressão entre parênteses consiste em uma expressão entre parênteses. ... Uma expressão entre parênteses é avaliada avaliando a expressão entre parênteses. Se a expressão entre parênteses indicar um espaço para nome ou tipo, ocorrerá um erro em tempo de compilação. Caso contrário, o resultado da expressão entre parênteses é o resultado da avaliação da expressão contida.

Como expressões entre parênteses não são listadas como uma declaração de expressão válida, não é uma declaração válida de acordo com a especificação. Por que os designers optaram por fazê-lo dessa maneira é uma incógnita, mas minha aposta é que parênteses não funcionam de maneira útil se toda a declaração estiver entre parênteses: stmte (stmt)são exatamente os mesmos.

Frank Bryce
fonte
4
@ Service: Se você ler atentamente, foi um pouco mais sutil do que isso. A chamada "Declaração de Expressão" é realmente uma declaração válida, e a especificação lista os tipos de expressões que podem ser declarações válidas. No entanto, repito pela especificação que o tipo de expressão chamado "expressão entre parênteses" NÃO está na lista de expressões válidas que podem ser usadas como declarações de expressão válidas.
Frank Bryce
4
O OPnada está perguntando sobre o design da linguagem. Ele só quer saber por que isso é um erro. A resposta é: porque não é uma afirmação válida .
Leandro
9
OK, meu mal. A resposta é: porque, de acordo com a especificação , não é uma declaração válida. Aí está: um motivo de design !
Leandro
3
A pergunta não está pedindo uma razão profunda, orientada ao design, por que a linguagem foi projetada para lidar com essa sintaxe dessa maneira - ele está perguntando por que um trecho de código é compilado quando outro trecho de código (que para iniciantes parece que deveria se comportar) identicamente) falha ao compilar. Este post responde isso.
Mago Xy
4
@Servy JohnCarpenter não está apenas dizendo "Você não pode fazer isso". Ele está dizendo, essas duas coisas pelas quais você ficou confuso não são as mesmas. j ++ é uma declaração de expressão, enquanto (j ++) é uma expressão entre parênteses. De repente, o OP agora sabe a diferença e o que são. Essa é uma boa resposta. Responde ao corpo de sua pergunta, não ao título. Eu acho que muita discussão vem da palavra "design" no título da pergunta, mas isso não precisa ser respondido pelos designers, apenas recolhido das especificações.
thinklarge
19

porque os colchetes ao redor do i++estão criando / definindo uma expressão .. como a mensagem de erro diz .. uma expressão simples não pode ser usada como uma declaração.

por que a linguagem foi projetada para ser assim? para evitar bugs, tendo expressões enganosas como instruções, que não produzem efeitos colaterais como ter o código

int j = 5;
j+1; 

a segunda linha não tem efeito (mas você pode não ter percebido). Mas, em vez de o compilador removê-lo (porque o código não é necessário), ele solicita explicitamente que você o remova (para que você fique ciente ou com o erro) OU corrija-o caso se esqueça de digitar algo.

editar :

para tornar a parte sobre bracked mais clara .. colchetes em c # (além de outros usos, como conversão de funções e chamada de função), são usados ​​para agrupar expressões e retornar uma única expressão (marca das subexpressões).

nesse nível de código, apenas staments são permitidos.

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

mas usando o colchete você o transforma como expressão

myTempExpression = (j++)

e isto

myTempExpression;

não é válido porque o compilador não pode garantir que a expressão seja efeito colateral. (não sem incorrer no problema de parada) ..

CaldasGSM
fonte
Sim, foi o que pensei no começo também. Mas isso faz ter um efeito colateral. Realiza pós-incremento da jvariável, certo?
usar o seguinte comando
1
j++;é uma declaração válida, mas usando os suportes que você está dizendo let me take this stement and turn it into an expression.. e expresions não são válidos nesse momento no código
CaldasGSM
É uma pena que não haja nenhuma forma de instrução "computar e ignorar", pois há ocasiões em que o código pode querer afirmar que um valor pode ser calculado sem gerar uma exceção, mas não se importa com o valor real calculado. Uma declaração de computação e ignorar não seria muito usada com frequência, mas tornaria clara a intenção do programador nesses casos.
Supercat 23/12