Avantages de l'utilisation de l'opérateur conditionnel?: (Ternaire)

101

Quels sont les avantages et les inconvénients de l'opérateur?: Par rapport à l'instruction if-else standard. Les plus évidents étant:

Conditionnel?: Opérateur

  • Plus court et plus concis lorsqu'il s'agit de comparaisons et d'affectations directes de valeurs
  • Ne semble pas aussi flexible que la construction if / else

Standard If / Else

  • Peut être appliqué à plus de situations (comme les appels de fonction)
  • Souvent inutilement longs

La lisibilité semble varier pour chacun en fonction de l'énoncé. Pendant un petit moment après avoir été exposé à l'opérateur?:, Il m'a fallu un certain temps pour comprendre exactement comment cela fonctionnait. Recommanderiez-vous de l'utiliser dans la mesure du possible, ou de m'en tenir à si / sinon étant donné que je travaille avec de nombreux non-programmeurs?

KChaloux
la source
8
Vous en avez déjà l'essentiel.
Byron Whitlock
1
@Nicholas Knight: Je suppose que l'OP signifie que vous ne pouvez pas faire, par exemple, SomeCheck() ? DoFirstThing() : DoSecondThing();vous devez utiliser l'expression pour renvoyer une valeur.
Dan Tao
6
Utilisez-le là où il est clair , tenez-vous-en à if / else si ce n'est pas le cas. La clarté du code devrait être votre principale considération.
hollsk
8
Avez-vous vu '??' encore? Sérieusement, si vous pensez que les ternaires sont cool ...
pdr
3
+1 pour ne pas l'appeler simplement "l'opérateur ternaire" comme beaucoup le font. Même s'il s'agit du seul opérateur ternaire (par opposition à unaire et binaire) en C #, ce n'est pas son nom.
John M Gant

Réponses:

122

Je recommanderais essentiellement de ne l'utiliser que lorsque l'instruction résultante est extrêmement courte et représente une augmentation significative de la concision par rapport à l'équivalent if / else sans sacrifier la lisibilité.

Bon exemple:

int result = Check() ? 1 : 0;

Mauvais exemple:

int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;
Dan Tao
la source
5
Bon appel, mais pour mémoire, c'est "concision".
mqp
6
@mquander, tu es sûr de ça? merriam-webster.com/dictionary/concise
Byron Whitlock
39
Je commence toujours par un simple et le rend plus complexe au fil du temps jusqu'à ce qu'il soit complètement illisible.
Jouke van der Maas
9
La lisibilité dans le deuxième exemple pourrait facilement être rectifiée avec un meilleur formatage. Mais, comme le recommande le PO, cela se résume à la lisibilité et à la concision par rapport à la verbosité.
Nathan Ernst
4
Cela ne fait pas partie de la question du PO, mais il est important de noter que vous ne pouvez pas returnfaire partie du résultat de l'opération ternaire. Par exemple: check() ? return 1 : return 0;ne fonctionnera pas, mais le return check() ? 1 : 0;fera. Toujours amusant de trouver ces petites bizarreries dans la programmation.
CSS
50

Ceci est à peu près couvert par les autres réponses, mais "c'est une expression" n'explique pas vraiment pourquoi c'est si utile ...

Dans des langages comme C ++ et C #, vous pouvez définir des champs en lecture seule locaux (dans un corps de méthode) en les utilisant. Cela n'est pas possible avec une instruction if / then conventionnelle car la valeur d'un champ en lecture seule doit être affectée dans cette instruction unique:

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

n'est pas la même chose que:

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

De la même manière, vous pouvez incorporer une expression tertiaire dans un autre code. En plus de rendre le code source plus compact (et dans certains cas plus lisible en conséquence), il peut également rendre le code machine généré plus compact et efficace:

MoveCar((shiftKeyDown) ? 10 : 1);

... peut générer moins de code que d'avoir à appeler la même méthode deux fois:

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

Bien sûr, c'est aussi une forme plus pratique et concise (moins de frappe, moins de répétition et peut réduire le risque d'erreurs si vous devez dupliquer des morceaux de code dans un if / else). Dans des cas de "modèle commun" propres comme celui-ci:

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

... il est tout simplement plus rapide à lire / analyser / comprendre (une fois que vous y êtes habitué) que l'équivalent si / else long et long, donc il peut vous aider à «grok» le code plus rapidement.

Bien sûr, ce n'est pas parce que c'est utile que c'est la meilleure chose à utiliser dans tous les cas. Je conseillerais de ne l'utiliser que pour de courts morceaux de code où la signification est claire (ou rendue plus claire) en utilisant ?:- si vous l'utilisez dans un code plus complexe, ou si vous imbriquez des opérateurs ternaires les uns dans les autres, cela peut rendre le code horriblement difficile à lire .

Jason Williams
la source
@JaminGrey "cela ne signifie pas que, lorsque la constante est créée, elle est définie sur 10 ou 1." Voulez-vous dire que cela signifie? Des commentaires incorrects peuvent causer plus de confusion aux nouveaux programmeurs C ++ que le problème que vous
tentiez de
5
Pour les futurs lecteurs qui rencontreront cela, par " const int speed = (shiftKeyDown)? 10: 1; ", cela signifie que lorsque la constante est créée pour la première fois , elle est définie sur 10 ou 1. Cela ne signifie pas que chaque fois la constante est accédée, elle effectue une vérification. (Juste au cas où un nouveau programmeur C ++ était confus)
Jamin Gray
2
... ou pour le dire autrement, a constest constant, c'est-à-dire qu'il ne peut pas être modifié après l'exécution de l'instruction dans laquelle il est déclaré.
Jason Williams
1
@JaminGrey. Cela ne devrait-il pas être le cas readonly? J'ai toujours pensé que cela constsignifiait « résolu au moment de la compilation et intégré partout où il était utilisé ».
Nolonar
1
@ColinWiseman, c'est un exemple pour illustrer comment?: Peut être utilisé. Je déclare spécifiquement que ce n'est pas parce que vous pouvez le faire que c'est nécessairement la «meilleure» chose à faire dans un cas particulier. Pour y arriver, le lecteur doit utiliser son cerveau chaque fois qu'il rencontre un cas où cela pourrait lui être utile.
Jason Williams
14

Je choisis généralement un opérateur ternaire quand j'aurais beaucoup de code en double autrement.

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

Avec un opérateur ternaire, ceci pourrait être accompli avec ce qui suit.

answer = compute(a > 0 ? a : -a, b, c, d, e); 
Ryan Bright
la source
12
Personnellement , je le ferais aVal = a > 0 ? a : -a; answer = compute(aVal,b,c,d,e);Surtout si b, c, det eaussi le traitement nécessaire.
corsiKa
10
Pourquoi utiliser un conditionnel dans cet exemple? Récupérez simplement Abs (a) et appelez une fois compute ().
Ash
2
Ouais, je n'ai pas créé le meilleur exemple. :)
Ryan Bright
Pour un débutant, cela n'a pas l'air équivalent. N'aurait-il pas besoin d'être answer = compute (a> 0? A, b, c, d, e: -a, b, c, d, e); ?
pbreitenbach
@pbreitenbach: non - c'est une question de priorité - le premier argument de compute(...)est a > 0 ? a : -1, qui est évalué séparément des autres arguments séparés par des virgules. Quoi qu'il en soit, malheureusement, C ++ n'a pas la notation que votre question pose pour gérer les "tuples" de valeurs séparées par des virgules, donc même a > 0 ? (a, b, c, d, e) : (-a, b, c, d, e)est illégal, et il n'y a rien de très similaire qui fonctionne sans changement de computelui-même.
Tony Delroy
12

Je trouve cela particulièrement utile lors du développement Web si je souhaite définir une variable sur une valeur envoyée dans la demande si elle est définie ou sur une valeur par défaut si ce n'est pas le cas.

wshato
la source
3
+1 valeurs par défaut dans le développement Web est un excellent exemple d'un bon endroit pour utiliser l'opérateur ternaire
Byron Whitlock
11

Une utilisation vraiment cool est:

x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;
aib
la source
10
Attention à cela en PHP, l'opérateur ternaire s'associe de manière incorrecte en PHP. Essentiellement, si fooest faux, alors le tout sera évalué à 4 sans faire les autres tests.
Tom Busby
4
@TomBusby - Wow. Encore une autre raison de détester PHP, si vous êtes quelqu'un qui déteste déjà PHP.
Todd Lehman
6

L'opérateur conditionnel est idéal pour les conditions courtes, comme ceci:

varA = boolB ? valC : valD;

Je l'utilise occasionnellement car cela prend moins de temps pour écrire quelque chose de cette façon ... malheureusement, ce branchement peut parfois être manqué par un autre développeur parcourant votre code. De plus, le code n'est généralement pas aussi court, donc j'aide généralement la lisibilité en mettant le? et: sur des lignes séparées, comme ceci:

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

Cependant, le gros avantage de l'utilisation des blocs if / else (et pourquoi je les préfère) est qu'il est plus facile d'entrer plus tard et d'ajouter une logique supplémentaire à la branche,

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

ou ajoutez une autre condition:

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

Donc, en fin de compte, c'est une question de commodité pour vous maintenant (plus courte à utiliser:?) Vs de commodité pour vous (et les autres) plus tard. C'est un appel au jugement ... mais comme tous les autres problèmes de formatage de code, la seule vraie règle est d'être cohérent et d'être visuellement courtois avec ceux qui doivent maintenir (ou noter!) Votre code.

(tout le code est compilé à l'œil)

iandisme
la source
5

Une chose à reconnaître lors de l'utilisation de l'opérateur ternaire qu'il s'agit d'une expression et non d'une instruction.

Dans les langages fonctionnels comme le schéma, la distinction n'existe pas:

(si (> ab) ab)

Conditionnel?: Opérateur "Ne semble pas aussi flexible que la construction if / else"

Dans les langages fonctionnels, c'est.

Lors de la programmation dans des langages impératifs, j'applique l'opérateur ternaire dans des situations où j'utiliserais généralement des expressions (affectation, instructions conditionnelles, etc.).

Ken Struys
la source
5

Bien que les réponses ci-dessus soient valides et que je conviens que la lisibilité soit importante, il y a 2 autres points à considérer:

  1. En C # 6, vous pouvez avoir des méthodes avec corps d'expression.

Cela rend particulièrement concis l'utilisation du ternaire:

string GetDrink(DayOfWeek day) 
   => day == DayOfWeek.Friday
      ? "Beer" : "Tea";
  1. Le comportement diffère en ce qui concerne la conversion de type implicite.

Si vous avez des types T1et T2que les deux peuvent être implicitement convertis en T, alors ce qui suit ne fonctionne pas :

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

(car le compilateur essaie de déterminer le type de l'expression ternaire et il n'y a pas de conversion entre T1et T2.)

En revanche, la if/elseversion ci-dessous fonctionne:

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

car T1est converti en Tet est doncT2

la-yumba
la source
5

Parfois, cela peut rendre l'attribution d'une valeur booléenne plus facile à lire au premier coup d'œil:

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

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

Si je mets une valeur et que je sais que ce sera toujours une ligne de code pour le faire, j'utilise généralement l'opérateur ternaire (conditionnel). S'il y a une chance que mon code et ma logique changent à l'avenir, j'utilise un if / else car c'est plus clair pour les autres programmeurs.

Un autre intérêt pour vous peut être le ?? opérateur .

Drharris
la source
4

L'avantage de l'opérateur conditionnel est qu'il s'agit d'un opérateur. En d'autres termes, il renvoie une valeur. Puisqu'il ifs'agit d'une instruction, elle ne peut pas renvoyer de valeur.

Gabe
la source
4

Je recommanderais de limiter l'utilisation de l'opérateur ternaire (? :) à une simple attribution d'une seule ligne logique if / else. Quelque chose ressemblant à ce modèle:

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

Peut être facilement converti en:

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

J'éviterais d'utiliser l'opérateur ternaire dans des situations qui nécessitent if / else if / else, imbriqué if / else, ou if / else une logique de branche qui aboutit à l'évaluation de plusieurs lignes. L'application de l'opérateur ternaire dans ces situations entraînerait probablement un code illisible, déroutant et ingérable. J'espère que cela t'aides.

HOCA
la source
2

L'utilisation de l '? opérateur dans par exemple. MS Visual C ++, mais c'est vraiment une chose spécifique au compilateur. Le compilateur peut en fait optimiser la branche conditionnelle dans certains cas.

Darklon
la source
2

Le scénario que je me retrouve le plus à utiliser est celui des valeurs par défaut et en particulier des retours

return someIndex < maxIndex ? someIndex : maxIndex;

Ce sont vraiment les seuls endroits où je trouve ça sympa, mais pour eux je le fais.

Cependant, si vous recherchez un booléen, cela peut parfois sembler une chose appropriée à faire:

bool hey = whatever < whatever_else ? true : false;

Parce que c'est si facile à lire et à comprendre, mais cette idée doit toujours être rejetée pour le plus évident:

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

Si vous avez besoin de plusieurs succursales dans la même condition, utilisez un if:

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

Si vous avez besoin de plusieurs branches avec des conditions différentes, alors si le nombre d'instructions fait boule de neige, vous voudrez utiliser le ternaire:

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

Vous pouvez également utiliser l'opérateur ternaire lors de l'initialisation.

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

Faire cela avec if est très compliqué:

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

Vous ne pouvez pas placer l'initialisation dans if / else, car cela change la portée. Mais les références et les variables const ne peuvent être liées qu'à l'initialisation.

Ben Voigt
la source
2

L'opérateur ternaire peut être inclus dans une rvalue, alors qu'un if-then-else ne le peut pas; d'autre part, un if-then-else peut exécuter des boucles et d'autres instructions, tandis que l'opérateur ternaire ne peut exécuter que des rvalues ​​(éventuellement vides).

Dans le même ordre d'idées, les && et || Les opérateurs autorisent certains modèles d'exécution qui sont plus difficiles à implémenter avec if-then-else. Par exemple, si l'on a plusieurs fonctions à appeler et que l'on souhaite exécuter un morceau de code si l'une d'entre elles échoue, cela peut être fait très bien en utilisant l'opérateur &&. Le faire sans cet opérateur nécessitera un code redondant, un goto ou une variable d'indicateur supplémentaire.

supercat
la source
1

Avec C # 7 , vous pouvez utiliser la nouvelle fonctionnalité de locals de référence pour simplifier l'affectation conditionnelle de variables compatibles ref. Alors maintenant, non seulement vous pouvez faire:

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

... mais aussi le plus merveilleux:

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

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

Cette ligne de code attribue la valeur de aà soit bou c, en fonction de la valeur de i.



Notes
1. La valeur r est le côté droit d'une affectation, la valeur qui est affectée.
2. l-value est la gauche côté -hand d'une affectation, la variable qui reçoit la valeur assignée.

Glenn Slayden
la source