Benefícios de usar o operador condicional?: (Ternário)

101

Quais são as vantagens e desvantagens do operador?: Em oposição à instrução if-else padrão. Os mais óbvios são:

Condicional?: Operador

  • Mais curto e mais conciso ao lidar com comparações e atribuições de valor direto
  • Não parece ser tão flexível quanto a construção if / else

If / Else padrão

  • Pode ser aplicado a mais situações (como chamadas de função)
  • Muitas vezes são desnecessariamente longos

A legibilidade parece variar para cada um, dependendo da declaração. Um pouco depois de ser exposto ao operador? :, demorei algum tempo para digerir exatamente como funcionava. Você recomendaria usá-lo sempre que possível, ou se limitar a if / else, visto que trabalho com muitos não programadores?

KChaloux
fonte
8
Você já tem a essência disso.
Byron Whitlock
1
@Nicholas Knight: Estou supondo que o OP significa que você não pode fazer, por exemplo, SomeCheck() ? DoFirstThing() : DoSecondThing();- você tem que usar a expressão para retornar um valor.
Dan Tao
6
Use-o onde for claro , fique com if / else se não for. A clareza do código deve ser sua principal consideração.
Hollsk
8
Você viu '??' ainda? Sério, se você acha que os ternários são legais ...
pdr
3
1 por não chamá-lo simplesmente de "o operador ternário" como muitos fazem. Embora seja o único operador ternário (em oposição a unário e binário) em C #, esse não é o seu nome.
John M Gant

Respostas:

122

Eu basicamente recomendaria usá-lo apenas quando a instrução resultante for extremamente curta e representar um aumento significativo na concisão sobre o equivalente if / else sem sacrificar a legibilidade.

Bom exemplo:

int result = Check() ? 1 : 0;

Mau exemplo:

int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;
Dan Tao
fonte
5
Boa escolha, mas para que conste, isso é "concisão".
mqp
6
@mquander, tem certeza disso? merriam-webster.com/dictionary/concise
Byron Whitlock
39
Sempre começo com um simples e o torno mais complexo com o tempo até que se torne completamente ilegível.
Jouke van der Maas
9
A legibilidade no segundo exemplo pode ser facilmente corrigida com uma formatação melhor. Mas, como o OP recomenda, tudo se resume à legibilidade e concisão versus verbosidade.
Nathan Ernst
4
Não faz parte da pergunta do OP, mas é importante ressaltar que você não pode ter uma returnparticipação no resultado da operação ternária. Por exemplo: check() ? return 1 : return 0;não vai funcionar, mas return check() ? 1 : 0;vai. É sempre divertido encontrar essas pequenas peculiaridades na programação.
CSS
50

Isso é praticamente coberto pelas outras respostas, mas "é uma expressão" não explica realmente por que isso é tão útil ...

Em linguagens como C ++ e C #, você pode definir campos somente leitura locais (dentro de um corpo de método) usando-os. Isso não é possível com uma instrução if / then convencional porque o valor de um campo somente leitura deve ser atribuído dentro dessa instrução única:

readonly int speed = (shiftKeyDown) ? 10 : 1;

não é o mesmo que:

readonly int speed;  
if (shifKeyDown)  
    speed = 10;    // error - can't assign to a readonly
else  
    speed = 1;     // error  

De maneira semelhante, você pode incorporar uma expressão terciária em outro código. Além de tornar o código-fonte mais compacto (e em alguns casos mais legível como resultado), ele também pode tornar o código de máquina gerado mais compacto e eficiente:

MoveCar((shiftKeyDown) ? 10 : 1);

... pode gerar menos código do que ter que chamar o mesmo método duas vezes:

if (shiftKeyDown)
    MoveCar(10);
else
    MoveCar(1);

Claro, também é uma forma mais conveniente e concisa (menos digitação, menos repetição e pode reduzir a chance de erros se você tiver que duplicar pedaços de código em um if / else). Em casos de "padrão comum" limpo como este:

object thing = (reference == null) ? null : reference.Thing;

... é simplesmente mais rápido de ler / analisar / compreender (uma vez que você esteja acostumado) do que o prolixo if / else equivalente, então pode ajudá-lo a 'grocar' código mais rápido.

Claro, só porque é útil não significa que seja a melhor coisa para usar em todos os casos. Aconselho usá-lo apenas para pequenos trechos de código onde o significado é claro (ou mais claro) usando ?:- se você usá-lo em um código mais complexo ou aninhar operadores ternários entre si, pode tornar o código terrivelmente difícil de ler .

Jason Williams
fonte
@JaminGrey "isso não significa que, quando a constante é criada, ela é definida como 10 ou 1." Você quer dizer faz média que? Comentários incorretos podem causar mais confusão para novos programadores C ++ do que o problema que você estava tentando resolver;)
Clonkex
5
Para os leitores futuros vindo através deste, por " velocidade int const = (shiftKeyDown) 10: 1; ", o que significa que, quando a constante é criada pela primeira vez , ele é definido para 10 ou 1. Ele não significa que cada vez a constante é acessada e faz uma verificação. (Apenas no caso de um programador C ++ mais recente estar confuso)
Jamin Gray
2
... ou dito de outra forma, a consté constante, ou seja, não pode ser alterado após a execução da instrução em que foi declarado.
Jason Williams
1
@JaminGrey. Não deveria ser readonlyassim? Sempre pensei que constsignificava " resolvido em tempo de compilação e alinhado sempre que usado ".
Nolonar
1
@ColinWiseman, é um exemplo para ilustrar como?: Pode ser usado. Afirmo especificamente que só porque você pode fazer isso, não significa que seja necessariamente a "melhor" coisa a fazer em qualquer caso específico. Para resolver isso, espera-se que o leitor use seu cérebro cada vez que se deparar com um caso em que possa ser útil para ele.
Jason Williams
14

Eu geralmente escolho um operador ternário quando, de outra forma, teria muito código duplicado.

if (a > 0)
    answer = compute(a, b, c, d, e);
else
    answer = compute(-a, b, c, d, e);

Com um operador ternário, isso pode ser realizado da seguinte maneira.

answer = compute(a > 0 ? a : -a, b, c, d, e); 
Ryan Bright
fonte
12
pessoalmente eu faria aVal = a > 0 ? a : -a; answer = compute(aVal,b,c,d,e);Especialmente se b, c, de enecessitou de tratamento também.
corsiKa
10
Por que usar uma condicional neste exemplo? Basta obter Abs (a) e chamar compute () uma vez.
Ash
2
Sim, eu não criei o melhor exemplo. :)
Ryan Bright
Para um novato, isso não parece equivalente. Não precisaria ser answer = compute (a> 0? A, b, c, d, e: -a, b, c, d, e); ?
pbreitenbach de
@pbreitenbach: não - é uma questão de precedência - o primeiro argumento para compute(...)é a > 0 ? a : -1, que é avaliado separadamente dos outros argumentos separados por vírgula. De qualquer forma, infelizmente C ++ não tem a notação que sua pergunta apresenta para lidar com "tuplas" de valores separados por vírgula, então even a > 0 ? (a, b, c, d, e) : (-a, b, c, d, e)é ilegal e não há nada muito semelhante que funcione sem mudanças em computesi mesmo.
Tony Delroy
12

Acho que é particularmente útil ao fazer desenvolvimento web se eu quiser definir uma variável para um valor enviado na solicitação, se estiver definido, ou para algum valor padrão, se não estiver.

wshato
fonte
3
+1 valores padrão em web dev é um excelente exemplo, um bom lugar para usar o operador ternário
Byron Whitlock
11

Um uso muito legal é:

x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;
aib
fonte
10
Tenha cuidado com isso no PHP, o operador ternário associa a maneira errada no PHP. Essencialmente, se foofor falso, então a coisa toda será avaliada como 4 sem fazer os outros testes.
Tom Busby,
4
@TomBusby - Uau. Mais uma razão para odiar PHP, se você já odeia PHP.
Todd Lehman
6

O operador condicional é ótimo para condições curtas, como esta:

varA = boolB ? valC : valD;

Eu uso ocasionalmente porque leva menos tempo para escrever algo dessa forma ... infelizmente, essa ramificação pode às vezes ser perdida por outro desenvolvedor navegando em seu código. Além disso, o código geralmente não é tão curto, então geralmente ajudo a legibilidade colocando o caractere? e: em linhas separadas, como esta:

doSomeStuffToSomething(shouldSomethingBeDone()
    ? getTheThingThatNeedsStuffDone()
    : getTheOtherThingThatNeedsStuffDone());

No entanto, a grande vantagem de usar blocos if / else (e por que eu os prefiro) é que é mais fácil entrar mais tarde e adicionar alguma lógica adicional ao branch,

if (shouldSomethingBeDone()) {
    doSomeStuffToSomething(getTheThingThatNeedsStuffDone());
    doSomeAdditionalStuff();
} else {
doSomeStuffToSomething(getTheOtherThingThatNeedsStuffDone());
}

ou adicione outra condição:

if (shouldSomethingBeDone()) {
    doSomeStuffToSomething(getTheThingThatNeedsStuffDone());
    doSomeAdditionalStuff();
} else if (shouldThisOtherThingBeDone()){
    doSomeStuffToSomething(getTheOtherThingThatNeedsStuffDone());
}

Portanto, no final das contas, trata-se de conveniência para você agora (mais curto de usar:?) Vs. conveniência para você (e outros) mais tarde. É um julgamento ... mas como todos os outros problemas de formatação de código, a única regra real é ser consistente e ser visualmente cortês com aqueles que precisam manter (ou avaliar!) Seu código.

(todo o código compilado)

iandismo
fonte
5

Uma coisa a reconhecer ao usar o operador ternário é que ele é uma expressão, não uma declaração.

Em linguagens funcionais como o esquema, a distinção não existe:

(if (> ab) ab)

Condicional?: Operador "Não parece ser tão flexível quanto a construção if / else"

Em linguagens funcionais, sim.

Ao programar em linguagens imperativas, aplico o operador ternário em situações em que normalmente usaria expressões (atribuição, declarações condicionais, etc.).

Ken Struys
fonte
5

Embora as respostas acima sejam válidas e eu concorde com a importância da legibilidade, há mais 2 pontos a serem considerados:

  1. No C # 6, você pode ter métodos com corpo de expressão.

Isso torna particularmente conciso usar o ternário:

string GetDrink(DayOfWeek day) 
   => day == DayOfWeek.Friday
      ? "Beer" : "Tea";
  1. O comportamento difere quando se trata de conversão implícita de tipo.

Se você tiver tipos T1e T2ambos puderem ser convertidos implicitamente T, o seguinte não funcionará:

T GetT() => true ? new T1() : new T2();

(porque o compilador tenta determinar o tipo da expressão ternária e não há conversão entre T1e T2.)

Por outro lado, a if/elseversão abaixo funciona:

T GetT()
{
   if (true) return new T1();
   return new T2();
}

porque T1é convertido em Te então éT2

la-yumba
fonte
5

Às vezes, pode tornar a atribuição de um valor bool mais fácil de ler à primeira vista:

// With
button.IsEnabled = someControl.HasError ? false : true;

// Without
button.IsEnabled = !someControl.HasError;
Tyler Pantuso
fonte
4

Se estou definindo um valor e sei que sempre haverá uma linha de código para fazer isso, normalmente uso o operador ternário (condicional). Se houver uma chance de meu código e lógica mudarem no futuro, eu uso um if / else, pois é mais claro para outros programadores.

De mais interesse para você pode ser o ?? operador .

drharris
fonte
4

A vantagem do operador condicional é que ele é um operador. Em outras palavras, ele retorna um valor. Como ifé uma declaração, não pode retornar um valor.

Gabe
fonte
4

Eu recomendaria limitar o uso do operador ternário (? :) à atribuição simples de uma única linha lógica if / else. Algo semelhante a este padrão:

if(<boolCondition>) {
    <variable> = <value>;
}
else {
    <variable> = <anotherValue>;
}

Pode ser facilmente convertido para:

<variable> = <boolCondition> ? <value> : <anotherValue>;

Eu evitaria usar o operador ternário em situações que exigem if / else if / else, if / else aninhado ou lógica de ramificação if / else que resulta na avaliação de várias linhas. Aplicar o operador ternário nessas situações provavelmente resultaria em código ilegível, confuso e não gerenciável. Espero que isto ajude.

HOCA
fonte
2

Há algum benefício de desempenho em usar o? operador, por exemplo. MS Visual C ++, mas isso é realmente uma coisa específica do compilador. O compilador pode realmente otimizar o desvio condicional em alguns casos.

Darklon
fonte
2

O cenário que mais me encontro usando é para valores padrão e especialmente em retornos

return someIndex < maxIndex ? someIndex : maxIndex;

Esses são realmente os únicos lugares que acho legais, mas para eles eu acho.

Porém, se você está procurando um booleano, isso às vezes pode parecer uma coisa apropriada a se fazer:

bool hey = whatever < whatever_else ? true : false;

Porque é tão fácil de ler e entender, mas essa ideia deve sempre ser jogada para o mais óbvio:

bool hey = (whatever < whatever_else);
Jimmy Hoffa
fonte
2

Se você precisar de vários branches na mesma condição, use um if:

if (A == 6)
  f(1, 2, 3);
else
  f(4, 5, 6);

Se você precisar de vários branches com condições diferentes, se a contagem de instruções for como uma bola de neve, você desejará usar o ternário:

f( (A == 6)? 1: 4, (B == 6)? 2: 5, (C == 6)? 3: 6 );

Além disso, você pode usar o operador ternário na inicialização.

const int i = (A == 6)? 1 : 4;

Fazer isso com if é muito confuso:

int i_temp;
if (A == 6)
   i_temp = 1;
else
   i_temp = 4;
const int i = i_temp;

Você não pode colocar a inicialização dentro de if / else, porque isso muda o escopo. Mas referências e variáveis ​​const só podem ser vinculadas na inicialização.

Ben Voigt
fonte
2

O operador ternário pode ser incluído em um rvalue, enquanto um if-then-else não pode; por outro lado, um if-then-else pode executar loops e outras instruções, enquanto o operador ternário só pode executar (possivelmente nulo) rvalues.

Em uma nota relacionada, o && e || operadores permitem alguns padrões de execução que são mais difíceis de implementar com if-then-else. Por exemplo, se alguém tem várias funções para chamar e deseja executar um trecho de código se alguma delas falhar, isso pode ser feito muito bem usando o operador &&. Fazer isso sem esse operador exigirá um código redundante, um goto ou uma variável de sinalização extra.

supergato
fonte
1

Com o C # 7 , você pode usar o novo recurso ref locais para simplificar a atribuição condicional de variáveis ​​compatíveis com ref. Então agora, você não só pode fazer:

int i = 0;

T b = default(T), c = default(T);

// initialization of C#7 'ref-local' variable using a conditional r-value⁽¹⁾

ref T a = ref (i == 0 ? ref b : ref c);

... mas também o extremamente maravilhoso:

// assignment of l-value⁽²⁾ conditioned by C#7 'ref-locals'

(i == 0 ? ref b : ref c) = a;

Essa linha de código atribui o valor de aa bou c, dependendo do valor de i.



Notas
1. O valor r é o lado direito de uma atribuição, o valor que é atribuído.
2. O valor-l é o lado esquerdo de uma atribuição, a variável que recebe o valor atribuído.

Glenn Slayden
fonte