Associatividade do operador ternário em C# - Posso confiar nisso?
-
21-09-2019 - |
Pergunta
Ahh, você não ama um bom abuso ternário? :) Considere a seguinte expressão:
true ? true : true ? false : false
Para aqueles que agora estão totalmente perplexos, posso dizer que isso avalia verdadeiro. Em outras palavras, é equivalente a isso:
true ? true : (true ? false : false)
Mas isso é confiável? Posso ter certeza de que, em algumas circunstâncias, não chegará a isso:
(true ? true : true) ? false : false
Alguns podem dizer - Bem, basta adicionar parênteses então ou não usá -lo completamente - afinal, é um fato bem conhecido que os operadores ternários são maus!
Claro que estão, mas há algumas circunstâncias quando eles realmente fazem sentido. Para os curiosos - estou colando código que compara dois objetos por uma série de propriedades. Seria muito bom se eu escreva frio assim:
obj1.Prop1 != obj2.Prop1 ? obj1.Prop1.CompareTo(obj2.Prop1) :
obj1.Prop2 != obj2.Prop2 ? obj1.Prop2.CompareTo(obj2.Prop2) :
obj1.Prop3 != obj2.Prop3 ? obj1.Prop3.CompareTo(obj2.Prop3) :
obj1.Prop4.CompareTo(obj2.Prop4)
Claro e conciso. Mas isso depende da associativa do operador ternário que funciona como no primeiro caso. Parênteses apenas fazia espaguete com isso.
Então - isso é especificado em algum lugar? Eu não consegui encontrar.
Solução
Sim, você pode confiar nisso (não apenas em C#, mas em tudo (que eu conheço) outros idiomas (exceto php … Vá imaginar) com um operador condicional) e seu caso de uso é na verdade uma prática bastante comum, embora algumas pessoas a abominem.
A seção relevante na ECMA-334 (o padrão C#) é 14.13 §3:
O operador condicional é associado à direita, o que significa que as operações são agrupadas da direita para a esquerda. [Exemplo: uma expressão do formulário
a ? b : c ? d : e
é avaliado comoa ? b : (c ? d : e)
. Exemplo final
Outras dicas
Se você tiver que perguntar, não. Qualquer pessoa que leia seu código terá que passar pelo mesmo processo que você fez, repetidamente, algum Tempo em que o código precisa ser analisado. Debugando esse código não é divertido. Eventualmente, ele será alterado para usar parênteses de qualquer maneira.
Ré: "Tente escrever a coisa toda com parênteses."
result = (obj1.Prop1 != obj2.Prop1 ? obj1.Prop1.CompareTo(obj2.Prop1) :
(obj1.Prop2 != obj2.Prop2 ? obj1.Prop2.CompareTo(obj2.Prop2) :
(obj1.Prop3 != obj2.Prop3 ? obj1.Prop3.CompareTo(obj2.Prop3) :
obj1.Prop4.CompareTo(obj2.Prop4))))
Esclarecimento:
- "Se vocês tem que perguntar, não. "
- "Qualquer pessoa lendo sua código..."
Seguir as convenções comuns em um projeto é como você mantém a consistência, o que melhora a legibilidade. Seria uma tarefa de tolo pensar que você pode escrever um código legível para todos - incluindo aqueles que nem conhecem o idioma!
Manter a consistência dentro de um projeto, no entanto, é uma meta útil, e não seguir as convenções aceitas de um projeto leva ao debate que prejudica a solução do problema real. Espera -se que aqueles que lêem seu código estejam cientes das convenções comuns e aceitas usadas no projeto e provavelmente sejam alguém que trabalhe diretamente nele. Se eles não os conhecem, espera -se que eles os aprendam e devem saber onde procurar ajuda.
Dito isto - se usar expressões ternárias sem parênteses é uma convenção comum e aceita em seu projeto, use -a, por todos os meios! O que você teve que perguntar indica que não é comum ou aceito em seu projeto. Se você deseja alterar as convenções em seu projeto, faça o obviamente inequívoco, marque -o como algo para discutir com outros membros do projeto e seguir em frente. Aqui isso significa usar parênteses ou usar if-else.
Um ponto final a ponderar, se parte do seu código parecer inteligente para você:
A depuração é duas vezes mais difícil do que escrever o código em primeiro lugar. Portanto, se você escrever o código da maneira mais inteligente possível, você é, por definição, não é inteligente o suficiente para depurar. - Brian W. Kernighan
A afirmação de que os parênteses prejudicam a legibilidade do código é uma suposição falsa. Acho a expressão entre parênteses muito mais clara. Pessoalmente, eu usaria os parênteses e/ou reformatados em várias linhas para melhorar a legibilidade. Reformatando sobre várias linhas e o uso de recuo pode até evitar a necessidade de parênteses. E, sim, você pode confiar no fato de que a ordem da associação é determinística, direita para a esquerda. Isso permite que a expressão avalie a esquerda para a direita da maneira esperada.
obj1.Prop1 != obj2.Prop1
? obj1.Prop1.CompareTo(obj2.Prop1)
: obj1.Prop2 != obj2.Prop2
? obj1.Prop2.CompareTo(obj2.Prop2)
: obj1.Prop3 != obj2.Prop3
? obj1.Prop3.CompareTo(obj2.Prop3)
: obj1.Prop4.CompareTo(obj2.Prop4);
Consulte o MSDN:http://msdn.microsoft.com/en-us/library/ty67wk28%28vs.80%29.aspx
"Se a condição for verdadeira, a primeira expressão é avaliada e se torna o resultado; se falsa, a segunda expressão é avaliada e se torna o resultado. Apenas uma das duas expressões é avaliada".
x = cond1 ? result1
: cond2 ? result2
: cond3 ? result3
: defaultResult;
vs.
if (cond1) x = result1;
else if (cond2) x = result2;
else if (cond3) x = result3;
else x = defaultResult;
Eu gosto da primeira.
Sim, você pode confiar na associativa do operador condicional. Está no manual, no link gentilmente fornecido pelo DCP, declarado como "o operador condicional é associativo correto", com um exemplo. E, como você sugeriu e eu e outros concordamos, o fato de você poder confiar nele permite um código mais claro.