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?


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;

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 .


@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); 

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.


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;

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)


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.).


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


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;

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 .


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.


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.


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.


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);

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.


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.


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.