Vorteile der Verwendung des bedingten ?: (Ternären) Operators

101

Was sind die Vor- und Nachteile des ?: Operators im Gegensatz zur Standard-if-else-Anweisung. Die offensichtlichen sind:

Bedingt?: Operator

  • Kürzer und prägnanter beim Umgang mit direkten Wertevergleichen und Zuordnungen
  • Scheint nicht so flexibel zu sein wie das if / else-Konstrukt

Standard If / Else

  • Kann auf mehrere Situationen angewendet werden (z. B. Funktionsaufrufe)
  • Oft sind sie unnötig lang

Die Lesbarkeit scheint je nach Aussage unterschiedlich zu sein. Nachdem ich dem Operator ?: Zum ersten Mal ausgesetzt war, brauchte ich eine Weile, um genau zu verstehen, wie es funktionierte. Würden Sie empfehlen, es wo immer möglich zu verwenden oder sich daran zu halten, wenn ich mit vielen Nicht-Programmierern zusammenarbeite?

KChaloux
quelle
8
Du hast schon das Wesentliche verstanden.
Byron Whitlock
1
@Nicholas Knight: Ich vermute, das OP bedeutet, dass Sie dies nicht tun können, z. B. SomeCheck() ? DoFirstThing() : DoSecondThing();- Sie müssen den Ausdruck verwenden, um einen Wert zurückzugeben.
Dan Tao
6
Verwenden Sie es, wo es klar ist , bleiben Sie bei if / else, wenn es nicht ist. Die Klarheit des Codes sollte Ihre Hauptüberlegung sein.
Hollsk
8
Hast du gesehen '??' noch? Im Ernst, wenn Sie denken, Ternaries sind cool ...
pdr
3
+1, weil man es nicht einfach "den ternären Operator" nennt, wie es viele tun. Obwohl es der einzige ternäre (im Gegensatz zu unären und binären) Operator in C # ist, ist dies nicht sein Name.
John M Gant

Antworten:

122

Ich würde grundsätzlich empfehlen, es nur zu verwenden, wenn die resultierende Aussage extrem kurz ist und eine signifikante Erhöhung der Prägnanz gegenüber dem if / else-Äquivalent darstellt, ohne die Lesbarkeit zu beeinträchtigen.

Gutes Beispiel:

int result = Check() ? 1 : 0;

Schlechtes Beispiel:

int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;
Dan Tao
quelle
5
Guter Anruf, aber fürs Protokoll, das ist "Prägnanz".
mqp
6
@mquander, bist du dir da sicher? merriam-webster.com/dictionary/concise
Byron Whitlock
39
Ich beginne immer mit einem einfachen und mache es im Laufe der Zeit komplexer, bis es völlig unlesbar ist.
Jouke van der Maas
9
Die Lesbarkeit im zweiten Beispiel könnte leicht durch eine bessere Formatierung korrigiert werden. Wie das OP empfiehlt, kommt es jedoch auf Lesbarkeit und Knappheit im Vergleich zur Ausführlichkeit an.
Nathan Ernst
4
Nicht Teil der Frage des OP, aber wichtig zu beachten ist die Tatsache, dass Sie nicht returnTeil des Ergebnisses der ternären Operation sein können. Zum Beispiel: check() ? return 1 : return 0;wird nicht funktionieren, wird aber return check() ? 1 : 0;. Es macht immer Spaß, diese kleinen Macken in der Programmierung zu finden.
CSS
50

Dies wird so ziemlich von den anderen Antworten abgedeckt, aber "es ist ein Ausdruck" erklärt nicht wirklich, warum das so nützlich ist ...

In Sprachen wie C ++ und C # können Sie damit lokale schreibgeschützte Felder (innerhalb eines Methodenkörpers) definieren. Dies ist mit einer herkömmlichen if / then-Anweisung nicht möglich, da der Wert eines schreibgeschützten Felds innerhalb dieser einzelnen Anweisung zugewiesen werden muss:

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

ist nicht dasselbe wie:

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

Auf ähnliche Weise können Sie einen tertiären Ausdruck in anderen Code einbetten. Der Quellcode wird nicht nur kompakter (und in einigen Fällen dadurch besser lesbar), sondern auch der generierte Maschinencode kompakter und effizienter:

MoveCar((shiftKeyDown) ? 10 : 1);

... kann weniger Code generieren, als dieselbe Methode zweimal aufrufen zu müssen:

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

Natürlich ist es auch eine bequemere und präzisere Form (weniger Eingabe, weniger Wiederholung und kann die Wahrscheinlichkeit von Fehlern verringern, wenn Sie Codeblöcke in einem if / else duplizieren müssen). In sauberen "Common Pattern" -Fällen wie diesen:

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

... es ist einfach schneller zu lesen / zu analysieren / zu verstehen (sobald Sie daran gewöhnt sind) als das langatmige if / else-Äquivalent, sodass Sie Code schneller "groken" können.

Natürlich nur , weil es ist nützlich , bedeutet nicht , es das Beste ist , zu verwenden , in jedem Fall. Ich würde empfehlen, es nur für kurze Codebits zu verwenden, bei denen die Bedeutung durch Verwendung klar (oder klarer) wird. ?:Wenn Sie es in komplexerem Code verwenden oder ternäre Operatoren ineinander verschachteln, kann es schrecklich schwierig sein, Code zu lesen .

Jason Williams
quelle
@JaminGrey "Das bedeutet nicht, dass die Konstante beim Erstellen entweder auf 10 oder 1 gesetzt wird." Meinst du , tut bedeuten? Falsche Kommentare können bei neuen C ++ - Programmierern mehr Verwirrung
stiften
5
Für zukünftige Leser, die darauf stoßen , bedeutet dies mit " const int speed = (shiftKeyDown)? 10: 1; ", dass die Konstante beim ersten Erstellen entweder auf 10 oder 1 gesetzt wird. Dies bedeutet nicht , dass dies jedes Mal der Fall ist Auf die Konstante wird zugegriffen, sie wird überprüft. (Nur für den Fall, dass ein neuerer C ++ - Programmierer verwirrt war)
Jamin Gray
2
... oder constanders ausgedrückt, a ist konstant, dh es kann nicht geändert werden, nachdem die Anweisung, in der es deklariert ist, ausgeführt wurde.
Jason Williams
1
@JaminGrey. Sollte es nicht sein readonly? Ich dachte immer const" zur Kompilierungszeit gelöst und überall dort eingebunden, wo es verwendet wird ".
Nolonar
1
@ColinWiseman, es ist ein Beispiel zu veranschaulichen , wie: kann verwendet werden. Ich sage ausdrücklich, dass es nicht unbedingt das "Beste" ist, was man in einem bestimmten Fall tun kann, nur weil man es kann. Um das herauszufinden, wird vom Leser erwartet, dass er sein Gehirn jedes Mal benutzt, wenn er auf einen Fall stößt, in dem es für ihn nützlich sein könnte.
Jason Williams
14

Normalerweise wähle ich einen ternären Operator, wenn ich sonst viel doppelten Code hätte.

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

Mit einem ternären Operator könnte dies wie folgt erreicht werden.

answer = compute(a > 0 ? a : -a, b, c, d, e); 
Ryan Bright
quelle
12
persönlich würde ich tun , aVal = a > 0 ? a : -a; answer = compute(aVal,b,c,d,e);Vor allem , wenn b, c, dund eBehandlung auch erforderlich.
CorsiKa
10
Warum in diesem Beispiel überhaupt eine Bedingung verwenden? Holen Sie sich einfach Abs (a) und rufen Sie compute () einmal auf.
Ash
2
Ja, ich habe nicht das beste Beispiel geschaffen. :)
Ryan Bright
Für einen Neuling sieht das nicht gleichwertig aus. Müsste es nicht answer = compute sein (a> 0? A, b, c, d, e: -a, b, c, d, e); ?
pbreitenbach
@pbreitenbach: nein - es ist eine Frage der Priorität - das erste Argument compute(...)ist a > 0 ? a : -1, das alle getrennt von den anderen durch Kommas getrennten Argumenten ausgewertet wird. Leider fehlt C ++ die Notation, die Ihre Frage für den Umgang mit "Tupeln" von durch Kommas getrennten Werten enthält. Dies ist sogar a > 0 ? (a, b, c, d, e) : (-a, b, c, d, e)illegal, und es gibt nichts sehr Ähnliches, das ohne Änderungen an sich computeselbst funktioniert .
Tony Delroy
12

Ich finde es besonders hilfreich bei der Webentwicklung, wenn ich eine Variable auf einen Wert setzen möchte, der in der Anforderung gesendet wird, wenn sie definiert ist, oder auf einen Standardwert, wenn dies nicht der Fall ist.

wshato
quelle
3
+1 Standardwerte in Web Dev ist ein hervorragendes Beispiel, ein guter Ort, um den ternären Operator zu verwenden
Byron Whitlock
11

Eine wirklich coole Verwendung ist:

x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;
aib
quelle
10
Seien Sie in PHP vorsichtig damit, der ternäre Operator ordnet in PHP den falschen Weg zu. Wenn dies foofalsch ist, wird das Ganze im Wesentlichen mit 4 bewertet, ohne die anderen Tests durchzuführen.
Tom Busby
4
@ TomBusby - Wow. Ein weiterer Grund, PHP zu hassen, wenn Sie jemand sind, der PHP bereits hasst.
Todd Lehman
6

Der bedingte Operator eignet sich hervorragend für kurze Bedingungen wie diese:

varA = boolB ? valC : valD;

Ich benutze es gelegentlich, weil es weniger Zeit braucht, etwas auf diese Weise zu schreiben ... Leider kann diese Verzweigung manchmal von einem anderen Entwickler übersehen werden, der Ihren Code durchsucht. Außerdem ist der Code normalerweise nicht so kurz, sodass ich normalerweise die Lesbarkeit unterstütze, indem ich das? und: in separaten Zeilen wie folgt:

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

Der große Vorteil bei der Verwendung von if / else-Blöcken (und warum ich sie bevorzuge) ist jedoch, dass es einfacher ist, später einzutreten und dem Zweig zusätzliche Logik hinzuzufügen.

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

oder fügen Sie eine andere Bedingung hinzu:

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

Letztendlich geht es also um Bequemlichkeit für Sie (kürzer zu verwenden :?) Im Vergleich zu Bequemlichkeit für Sie (und andere) später. Es ist ein Urteilsspruch ... aber wie bei allen anderen Problemen bei der Code-Formatierung besteht die einzige wirkliche Regel darin, konsistent und visuell höflich gegenüber denen zu sein, die Ihren Code pflegen (oder bewerten!) Müssen.

(alle Code Auge kompiliert)

iandisme
quelle
5

Bei der Verwendung des ternären Operators ist zu beachten, dass es sich um einen Ausdruck und nicht um eine Anweisung handelt.

In funktionalen Sprachen wie Schema existiert die Unterscheidung nicht:

(if (> ab) ab)

Bedingt ?: Operator "Scheint nicht so flexibel zu sein wie das if / else-Konstrukt"

In funktionalen Sprachen ist es.

Beim Programmieren in imperativen Sprachen wende ich den ternären Operator in Situationen an, in denen ich normalerweise Ausdrücke (Zuweisung, bedingte Anweisungen usw.) verwenden würde.

Ken Struys
quelle
5

Obwohl die obigen Antworten gültig sind und ich der Lesbarkeit zustimme, sind zwei weitere Punkte zu beachten:

  1. In C # 6 können Sie Ausdrucksmethoden verwenden.

Dies macht es besonders prägnant, das ternäre zu verwenden:

string GetDrink(DayOfWeek day) 
   => day == DayOfWeek.Friday
      ? "Beer" : "Tea";
  1. Das Verhalten unterscheidet sich bei der impliziten Typkonvertierung.

Wenn Sie Typen haben T1und T2dass beide implizit konvertiert werden können T, dann ist die unten tut nicht Arbeit:

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

(weil der Compiler versucht, den Typ des ternären Ausdrucks zu bestimmen, und es keine Konvertierung zwischen T1und gibt T2.)

Auf der anderen Seite if/elsefunktioniert die folgende Version:

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

weil T1konvertiert wird Tund so istT2

la-yumba
quelle
5

Manchmal kann es die Zuweisung eines Bool-Werts auf den ersten Blick leichter lesbar machen:

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

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

Wenn ich einen Wert einstelle und weiß, dass dies immer eine Codezeile ist, verwende ich normalerweise den ternären (bedingten) Operator. Wenn sich mein Code und meine Logik in Zukunft möglicherweise ändern, verwende ich ein if / else, da dies für andere Programmierer klarer ist.

Von weiterem Interesse für Sie kann die ?? Betreiber .

Drharris
quelle
4

Der Vorteil des bedingten Operators besteht darin, dass er ein Operator ist. Mit anderen Worten, es wird ein Wert zurückgegeben. Da ifes sich um eine Anweisung handelt, kann sie keinen Wert zurückgeben.

Gabe
quelle
4

Ich würde empfehlen, die Verwendung des ternären (? :) Operators auf die einfache Zuweisung einer einzelnen Zeile zu beschränken, wenn / sonst Logik. Etwas ähnlich diesem Muster:

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

Könnte leicht konvertiert werden zu:

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

Ich würde es vermeiden, den ternären Operator in Situationen zu verwenden, in denen if / else if / else, verschachtelte if / else oder if / else-Verzweigungslogik erforderlich sind, die zur Auswertung mehrerer Zeilen führt. Das Anwenden des ternären Operators in diesen Situationen würde wahrscheinlich zu unlesbarem, verwirrendem und nicht verwaltbarem Code führen. Hoffe das hilft.

HOCA
quelle
2

Es gibt einige Leistungsvorteile bei der Verwendung des? Betreiber in z. MS Visual C ++, aber dies ist wirklich eine compilerspezifische Sache. In einigen Fällen kann der Compiler den bedingten Zweig tatsächlich optimieren.

Darklon
quelle
2

Das Szenario, in dem ich es am häufigsten verwende, betrifft Standardwerte und insbesondere Rückgaben

return someIndex < maxIndex ? someIndex : maxIndex;

Das sind wirklich die einzigen Orte, an denen ich es schön finde, aber für sie tue ich es.

Wenn Sie nach einem Booleschen Wert suchen, scheint dies manchmal eine angemessene Vorgehensweise zu sein:

bool hey = whatever < whatever_else ? true : false;

Weil es so einfach zu lesen und zu verstehen ist, aber diese Idee sollte immer für das Offensichtliche geworfen werden:

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

Wenn Sie mehrere Zweige unter derselben Bedingung benötigen, verwenden Sie ein if:

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

Wenn Sie mehrere Zweige mit unterschiedlichen Bedingungen benötigen und die Anzahl der Anweisungen Schneeball schlagen würde, sollten Sie das ternäre verwenden:

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

Sie können auch den ternären Operator bei der Initialisierung verwenden.

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

Das zu tun mit if ist sehr chaotisch:

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

Sie können die Initialisierung nicht in if / else einfügen, da dies den Bereich ändert. Referenzen und const-Variablen können jedoch nur bei der Initialisierung gebunden werden.

Ben Voigt
quelle
2

Der ternäre Operator kann in einen r-Wert aufgenommen werden, ein Wenn-Dann-Sonst nicht. Andererseits kann ein Wenn-Dann-Sonst Schleifen und andere Anweisungen ausführen, während der ternäre Operator nur (möglicherweise ungültige) r-Werte ausführen kann.

In einem verwandten Hinweis sind die && und || Operatoren erlauben einige Ausführungsmuster, die mit if-then-else schwieriger zu implementieren sind. Wenn Sie beispielsweise mehrere Funktionen aufrufen müssen und einen Code ausführen möchten, wenn eine davon fehlschlägt, können Sie dies mit dem Operator && tun. Wenn Sie auf diesen Operator verzichten, ist entweder redundanter Code, ein goto oder eine zusätzliche Flag-Variable erforderlich.

Superkatze
quelle
1

Mit C # 7 können Sie die neue Ref-Locals- Funktion verwenden, um die bedingte Zuweisung von Ref-kompatiblen Variablen zu vereinfachen. Jetzt können Sie also nicht nur Folgendes tun:

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

... aber auch das extrem wundervolle:

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

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

Diese Codezeile weist den Wert von aentweder boder zu c, abhängig vom Wert von i.



Anmerkungen
1. Der r-Wert ist die rechte Seite einer Zuweisung, der Wert, der zugewiesen wird.
2. l-Wert ist die linke Seite einer Zuweisung, die Variable, die den zugewiesenen Wert erhält.

Glenn Slayden
quelle