Isso é permitido:
int a, b, c;
a = b = c = 16;
string s = null;
while ((s = "Hello") != null) ;
Para meu entendimento, a atribuição s = ”Hello”;
deve apenas “Hello”
ser atribuída s
, mas a operação não deve retornar nenhum valor. Se isso fosse verdade, ((s = "Hello") != null)
produziria um erro, pois null
seria comparado a nada.
Qual é o raciocínio por trás da permissão de declarações de atribuição para retornar um valor?
while ((s = GetWord()) != null) Process(s);
não é).Respostas:
Sua compreensão é 100% incorreta. Você pode explicar por que acredita nessa coisa falsa?
Primeiro, as instruções de atribuição não produzem um valor. Expressões de atribuição produzem um valor. Uma expressão de atribuição é uma declaração legal; há apenas algumas expressões que são declarações legais em C #: aguarda uma expressão; expressões de construção, incremento, decremento, chamada e atribuição de instância podem ser usadas onde uma declaração é esperada.
Existe apenas um tipo de expressão em C # que não produz algum tipo de valor, a saber, uma invocação de algo digitado como retorno nulo. (Ou, equivalentemente, uma espera de uma tarefa sem valor de resultado associado.) Todos os outros tipos de expressão produzem um valor ou variável ou referência ou acesso a propriedades ou acesso a eventos e assim por diante.
Observe que todas as expressões legais como declarações são úteis para seus efeitos colaterais . Essa é a principal ideia aqui, e acho que talvez seja a causa da sua intuição de que atribuições devem ser declarações e não expressões. Idealmente, teríamos exatamente um efeito colateral por declaração e nenhum efeito colateral em uma expressão. Ele é um pouco estranho que o código-efectuar lado pode ser usado num contexto de expressão de todo.
O raciocínio por trás da permissão desse recurso é porque (1) ele é frequentemente conveniente e (2) é idiomático em idiomas semelhantes a C.
Pode-se notar que a pergunta foi feita: por que isso é idiomático em linguagens do tipo C?
Infelizmente, Dennis Ritchie não está mais disponível para perguntar, mas meu palpite é que uma tarefa quase sempre deixa para trás o valor que acabou de ser atribuído em um registro. C é um tipo de linguagem muito "próximo à máquina". Parece plausível e de acordo com o design de C que haja um recurso de linguagem que basicamente significa "continuar usando o valor que acabei de atribuir". É muito fácil escrever um gerador de código para esse recurso; você apenas continua usando o registro que armazenou o valor que foi atribuído.
fonte
=
/==
typo, que é facilmente resolvido com a proibição de usar o valor de, a=
menos que esteja entre parênteses. por exemplo,if ((x = y))
ouif ((x = y) == true)
é permitido, masif (x = y)
não é.Você não forneceu a resposta? É para ativar exatamente os tipos de construções que você mencionou.
Um caso comum em que essa propriedade do operador de atribuição é usada é a leitura de linhas de um arquivo ...
fonte
return _myBackingField ?? (_myBackingField = CalculateBackingField());
muito menos palha do que verificar se há nulos e atribuições.Meu uso favorito de expressões de atribuição é para propriedades inicializadas preguiçosamente.
fonte
??=
sintaxe.Por um lado, permite encadear suas atribuições, como no seu exemplo:
Por outro lado, permite atribuir e verificar um resultado em uma única expressão:
Ambos são possíveis motivos duvidosos, mas definitivamente existem pessoas que gostam dessas construções.
fonte
return (HttpContext.Current.Items["x"] = myvar);
Além dos motivos já mencionados (encadeamento de atribuição, configuração e teste dentro de loops while, etc.), para usar corretamente a
using
instrução, você precisa deste recurso:O MSDN desencoraja a declaração do objeto descartável fora da instrução using, pois ele permanecerá no escopo mesmo depois de descartado (consulte o artigo do MSDN que eu link).
fonte
using
. Todos estes são legais:using (X x = new X())
,using (x = new X())
,using (x)
. No entanto, neste exemplo, o conteúdo da instrução using é uma sintaxe especial que não depende de atribuição que retorna um valor -Font font3 = new Font("Arial", 10.0f)
não é uma expressão e não é válida em nenhum lugar que espere expressões.Eu gostaria de elaborar um ponto específico que Eric Lippert fez em sua resposta e colocar os holofotes em uma ocasião específica que nunca foi abordada por mais ninguém. Eric disse:
Eu gostaria de dizer que a atribuição sempre deixará para trás o valor que tentamos atribuir ao nosso operando esquerdo. Não apenas "quase sempre". Mas não sei porque não encontrei esse problema comentado na documentação. Teoricamente, pode ser um procedimento implementado muito eficaz para "deixar para trás" e não reavaliar o operando esquerdo, mas é eficiente?
'Eficiente' sim para todos os exemplos construídos até agora nas respostas deste tópico. Mas eficiente no caso de propriedades e indexadores que usam acessadores get e set? De modo nenhum. Considere este código:
Aqui temos uma propriedade que nem sequer é um invólucro para uma variável privada. Sempre que for chamado, ele retornará verdadeiro, sempre que alguém tentar definir seu valor, nada fará. Assim, sempre que essa propriedade for avaliada, ele deve ser verdadeiro. Vamos ver o que acontece:
Adivinha o que imprime? Imprime
Unexpected!!
. Como se vê, o acessador definido é realmente chamado, o que não faz nada. Mas, posteriormente, o acessador get nunca é chamado. A atribuição simplesmente deixa para trás ofalse
valor que tentamos atribuir à nossa propriedade. E essefalse
valor é o que a instrução if avalia.Terminarei com um exemplo do mundo real que me levou a pesquisar esse problema. Criei um indexador que era um invólucro conveniente para uma coleção (
List<string>
) que uma classe minha tinha como variável privada.O parâmetro enviado ao indexador era uma sequência que deveria ser tratada como um valor em minha coleção. O acessador get simplesmente retornaria true ou false se esse valor existisse na lista ou não. Portanto, o acessador get era outra maneira de usar o
List<T>.Contains
método.Se o acessador de conjunto do indexador fosse chamado com uma string como argumento e o operando certo fosse um bool
true
, ele adicionaria esse parâmetro à lista. Mas se o mesmo parâmetro fosse enviado ao acessador e o operando certo fosse um boolfalse
, ele excluiria o elemento da lista. Assim, o acessador definido foi usado como uma alternativa conveniente para ambosList<T>.Add
eList<T>.Remove
.Eu pensei que tinha uma "API" limpa e compacta, envolvendo a lista com minha própria lógica implementada como gateway. Com a ajuda de um indexador sozinho, eu poderia fazer muitas coisas com algumas combinações de teclas. Por exemplo, como posso tentar adicionar um valor à minha lista e verificar se ela está lá? Eu pensei que esta era a única linha de código necessária:
Mas, como meu exemplo anterior mostrou, o acessador get, que deveria ver se o valor realmente está na lista, nem foi chamado. O
true
valor sempre foi deixado para trás, destruindo efetivamente qualquer lógica que eu havia implementado no meu acessador.fonte
Se a atribuição não retornasse um valor, a linha
a = b = c = 16
também não funcionaria.Também poder escrever coisas como isso
while ((s = readLine()) != null)
pode ser útil às vezes.Portanto, a razão por trás de permitir que a atribuição retorne o valor atribuído é permitir que você faça essas coisas.
fonte
=
ocorrência de uma expressão que não está entre parênteses (sem contar os parênteses da instrução if / while). O gcc dá esses avisos e, com isso, basicamente elimina essa classe de bugs dos programas C / C ++ que são compilados. É uma pena que outros escritores de compiladores tenham prestado tão pouca atenção a isso e a várias outras boas idéias no gcc.Acho que você está entendendo mal como o analisador interpretará essa sintaxe. A tarefa será avaliada primeiro e o resultado será comparado a NULL, ou seja, a instrução é equivalente a:
Como outros já apontaram, o resultado de uma atribuição é o valor atribuído. Acho difícil imaginar a vantagem de ter
((s = "Hello") != null)
e
não ser equivalente ...
fonte
Eu acho que o principal motivo é a semelhança (intencional) com C ++ e C. Fazer com que o operador de atribuição (e muitas outras construções de linguagem) se comporte como seus colegas em C ++ apenas segue o princípio da menor surpresa e qualquer programador vindo de outro a linguagem colchete pode usá-los sem gastar muito em pensar. Ser fácil de escolher para programadores de C ++ foi um dos principais objetivos de design do C #.
fonte
Pelas duas razões que você incluiu em sua postagem
1), para que você possa fazer
a = b = c = 16
2) para testar se uma tarefa foi bem-sucedida
if ((s = openSomeHandle()) != null)
fonte
O fato de que 'a ++' ou 'printf ("foo")' pode ser útil como uma declaração independente ou como parte de uma expressão maior significa que C deve permitir a possibilidade de que os resultados da expressão possam ou não ser usava. Dado isso, existe uma noção geral de que expressões que podem "retornar" utilmente um valor também podem fazê-lo. O encadeamento de atribuição pode ser um pouco "interessante" em C, e ainda mais interessante em C ++, se todas as variáveis em questão não tiverem exatamente o mesmo tipo. Provavelmente, é melhor evitar esses usos.
fonte
Outro ótimo exemplo de caso de uso, eu uso isso o tempo todo:
fonte
Uma vantagem extra que não vejo nas respostas aqui é que a sintaxe da atribuição é baseada em aritmética.
Agora
x = y = b = c = 2 + 3
significa algo diferente em aritmética do que uma linguagem no estilo C; na aritmética sua afirmação, que afirmam que x é igual a y etc. e em uma linguagem de estilo C é uma instrução que as marcas x igual a y etc. depois que ele é executado.Dito isto, ainda existe relação suficiente entre a aritmética e o código, de modo que não faz sentido desaprovar o que é natural na aritmética, a menos que haja uma boa razão. (A outra coisa que as linguagens no estilo C tiraram do uso do símbolo de igual é o uso de == para comparação de igualdade. Aqui, porém, porque o mais à direita == retorna um valor que esse tipo de encadeamento seria impossível.)
fonte
a = b = c
meios quea
eb
ec
são a mesma coisa. Ao aprender uma linguagem de estilo C aprendemos que depoisa = b = c
,a
eb
são a mesma coisa quec
. Certamente há uma diferença na semântica, como minha própria resposta diz, mas ainda quando criança aprendi a programar em uma linguagem usada=
para tarefas, mas não permitia quea = b = c
isso parecesse irracional para mim, no entanto ...=
para comparação de igualdade; portanto, issoa = b = c
teria que significar o quea = b == c
significa em linguagens no estilo C. Achei o encadeamento permitido em C muito mais intuitivo, porque eu poderia fazer uma analogia com a aritmética.Gosto de usar o valor de retorno da atribuição quando preciso atualizar várias coisas e retornar se houve alguma alteração:
Tenha cuidado, porém. Você pode pensar que pode reduzi-lo para isso:
Mas isso realmente irá parar de avaliar as declarações ou depois que encontrar a primeira verdade. Nesse caso, isso significa que ele para de atribuir valores subseqüentes depois de atribuir o primeiro valor diferente.
Veja https://dotnetfiddle.net/e05Rh8 para brincar com isso
fonte