Korzyści z używania operatora warunkowego?: (Trójskładnikowego)

101

Jakie są zalety i wady operatora?: W porównaniu ze standardową instrukcją if-else. Te oczywiste to:

Warunkowe?: Operator

  • Krótsze i bardziej zwięzłe w przypadku bezpośrednich porównań wartości i przypisań
  • Nie wydaje się być tak elastyczny jak konstrukcja if / else

Standardowe Jeśli / Jeszcze

  • Może być stosowany w większej liczbie sytuacji (takich jak wywołania funkcji)
  • Często są niepotrzebnie długie

Czytelność wydaje się różna dla każdego w zależności od stwierdzenia. Przez chwilę po pierwszym kontakcie z operatorem?: Zajęło mi trochę czasu, zanim dokładnie przeanalizowałem, jak to działa. Czy poleciłbyś używanie go wszędzie tam, gdzie to możliwe, czy trzymanie się if / else, biorąc pod uwagę, że pracuję z wieloma nie-programistami?

KChaloux
źródło
8
Masz już sedno tego.
Byron Whitlock,
1
@Nicholas Knight: Domyślam się, że OP oznacza, że ​​nie możesz tego zrobić, np. SomeCheck() ? DoFirstThing() : DoSecondThing();- musisz użyć wyrażenia, aby zwrócić wartość.
Dan Tao
6
Użyj go tam , gdzie jest to jasne , trzymaj się if / else, jeśli nie jest. Najważniejszą kwestią jest przejrzystość kodu.
hollsk
8
Widziałeś '??' jeszcze? Poważnie, jeśli uważasz, że trójskładniki są fajne ...
pdr
3
+1 za to, że nie nazywa się go po prostu „operatorem trójskładnikowym”, jak wielu to robi. Mimo że jest to jedyny operator trójargumentowy (w przeciwieństwie do jednoargumentowego i binarnego) w C #, to nie jest jego nazwa.
John M Gant

Odpowiedzi:

122

Zasadniczo zalecałbym używanie go tylko wtedy, gdy wynikowe stwierdzenie jest wyjątkowo krótkie i przedstawia znaczny wzrost zwięzłości w stosunku do odpowiednika if / else bez utraty czytelności.

Dobry przykład:

int result = Check() ? 1 : 0;

Zły przykład:

int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;
Dan Tao
źródło
5
Dobra decyzja, ale tak naprawdę to „zwięzłość”.
mqp
6
@mquander, jesteś tego pewien? merriam-webster.com/dictionary/concise
Byron Whitlock
39
Zawsze zaczynam od prostego iz czasem komplikuję go, aż stanie się całkowicie nieczytelny.
Jouke van der Maas
9
Czytelność w drugim przykładzie można łatwo poprawić dzięki lepszemu formatowaniu. Ale, jak zaleca OP, sprowadza się to do czytelności i zwięzłości w porównaniu do gadatliwości.
Nathan Ernst
4
Nie jest częścią pytania PO, ale należy zauważyć, że nie można mieć returnudziału w wyniku operacji trójskładnikowej. Na przykład: check() ? return 1 : return 0;nie zadziała, ale return check() ? 1 : 0;będzie. Zawsze fajnie jest znaleźć te małe dziwactwa w programowaniu.
CSS
50

Jest to prawie omówione w innych odpowiedziach, ale „to wyrażenie” nie wyjaśnia, dlaczego jest to tak przydatne ...

W językach takich jak C ++ i C # można za ich pomocą definiować lokalne pola tylko do odczytu (w treści metody). Nie jest to możliwe w przypadku konwencjonalnej instrukcji jeśli / to, ponieważ wartość pola tylko do odczytu musi zostać przypisana w ramach tej pojedynczej instrukcji:

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

to nie to samo co:

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

W podobny sposób możesz osadzić trzeciorzędne wyrażenie w innym kodzie. Oprócz tego, że kod źródłowy jest bardziej zwarty (aw niektórych przypadkach bardziej czytelny), może również sprawić, że wygenerowany kod maszynowy będzie bardziej zwarty i wydajny:

MoveCar((shiftKeyDown) ? 10 : 1);

... może generować mniej kodu niż konieczność dwukrotnego wywoływania tej samej metody:

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

Oczywiście jest to również wygodniejsza i bardziej zwięzła forma (mniej pisania, mniej powtórzeń i może zmniejszyć ryzyko błędów, jeśli musisz zduplikować fragmenty kodu w instrukcji if / else). W czystych „typowych wzorcach”, takich jak ten:

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

... jest po prostu szybszy do czytania / analizowania / rozumienia (kiedy już się do tego przyzwyczaisz) niż rozwlekły odpowiednik if / else, więc może pomóc ci w szybszym "grokowaniu" kodu.

Oczywiście tylko dlatego, że jest użyteczny , nie oznacza, że ​​najlepiej jest go używać w każdym przypadku. Radziłbym używać go tylko do krótkich fragmentów kodu, których znaczenie jest jasne (lub bardziej zrozumiałe), używając ?:- jeśli użyjesz go w bardziej złożonym kodzie lub zagnieżdżasz w sobie operatory trójskładnikowe, może to bardzo utrudnić odczytanie kodu .

Jason Williams
źródło
@JaminGrey "to nie znaczy, że po utworzeniu stałej jest ustawiana na 10 lub 1." Masz na myśli, że to znaczy? Niepoprawne komentarze mogą spowodować więcej zamieszania wśród nowych programistów C ++ niż problem, który próbowałeś wyjaśnić;)
Clonkex
5
Dla przyszłych czytelników, którzy zetkną się z tym problemem , przez „ const int speed = (shiftKeyDown)? 10: 1; ” oznacza to, że gdy stała jest tworzona po raz pierwszy , jest ustawiana na 10 lub 1. Nie oznacza to, że za każdym razem stała jest dostępna, sprawdza. (Wystarczy, że nowszy programista C ++ był zdezorientowany)
Jamin Gray
2
... inaczej mówiąc, a constjest stałe, tj. nie można go zmienić po wykonaniu instrukcji, w której jest zadeklarowane.
Jason Williams
1
@JaminGrey. Nie powinno tak być readonly? Zawsze myślałem const, że oznacza „ rozwiązany w czasie kompilacji i wbudowany wszędzie tam, gdzie jest używany ”.
Nolonar
1
@ColinWiseman, to przykład ilustrujący, jak można użyć ?: . W szczególności stwierdzam, że tylko dlatego, że możesz to zrobić, nie oznacza to, że jest to koniecznie „najlepsza” rzecz do zrobienia w konkretnym przypadku. Aby to rozwiązać, od czytelnika oczekuje się, że użyje swojego mózgu za każdym razem, gdy napotka przypadek, w którym może być dla niego przydatny.
Jason Williams
14

Zwykle wybieram operator trójskładnikowy, gdy w przeciwnym razie miałbym dużo zduplikowanego kodu.

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

W przypadku operatora trójskładnikowego można to osiągnąć w następujący sposób.

answer = compute(a > 0 ? a : -a, b, c, d, e); 
Ryan Bright
źródło
12
Osobiście zrobiłbym aVal = a > 0 ? a : -a; answer = compute(aVal,b,c,d,e);Zwłaszcza jeśli b, c, di ewymagał leczenia zbyt.
corsiKa
10
Po co w ogóle używać warunku w tym przykładzie? Po prostu pobierz Abs (a) i wywołaj raz compute ().
Ash
2
Tak, nie stworzyłem najlepszego przykładu. :)
Ryan Bright
Dla początkującego nie wygląda to równoważnie. Czy nie musiałoby to być answer = compute (a> 0? A, b, c, d, e: -a, b, c, d, e); ?
pbreitenbach
@pbreitenbach: nie - to kwestia pierwszeństwa - pierwszy argument do compute(...)to a > 0 ? a : -1, który jest oceniany oddzielnie od pozostałych argumentów oddzielonych przecinkami. W każdym razie, niestety, C ++ nie ma notacji, jaką zakłada twoje pytanie do obsługi „krotek” wartości oddzielonych przecinkami, więc nawet a > 0 ? (a, b, c, d, e) : (-a, b, c, d, e)jest nielegalne i nie ma nic podobnego, co działałoby bez zmian compute.
Tony Delroy
12

Uważam to za szczególnie pomocne podczas tworzenia stron internetowych, jeśli chcę ustawić zmienną na wartość wysłaną w żądaniu, jeśli jest zdefiniowana, lub na jakąś wartość domyślną, jeśli nie jest.

wshato
źródło
3
Domyślne wartości +1 w programowaniu stron internetowych to doskonały przykład dobre miejsce na użycie operatora trójskładnikowego
Byron Whitlock
11

Naprawdę fajne użycie to:

x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;
aib
źródło
10
Uważaj na to w PHP, operator trójskładnikowy kojarzy niewłaściwy sposób w PHP. Zasadniczo, jeśli foojest fałszywe, całość zostanie oceniona na 4 bez wykonywania innych testów.
Tom Busby
4
@TomBusby - Wow. Kolejny powód, aby nienawidzić PHP, jeśli jesteś kimś, kto już go nienawidzi.
Todd Lehman
6

Operator warunkowy świetnie sprawdza się w krótkich warunkach, takich jak ten:

varA = boolB ? valC : valD;

Używam go od czasu do czasu, ponieważ napisanie czegoś w ten sposób zajmuje mniej czasu ... niestety to rozgałęzienie może czasami zostać pominięte przez innego programistę przeglądającego Twój kod. Poza tym kod nie jest zwykle taki krótki, więc zwykle pomagam w czytelności, umieszczając znak? i: w oddzielnych wierszach, na przykład:

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

Jednak dużą zaletą używania bloków if / else (i dlaczego je preferuję) jest to, że łatwiej jest wejść później i dodać dodatkową logikę do gałęzi,

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

lub dodaj kolejny warunek:

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

Ostatecznie chodzi więc o wygodę dla Ciebie teraz (krótszą w użyciu:?) Vs. wygodę dla Ciebie (i innych) później. To kwestia oceny ... ale podobnie jak w przypadku wszystkich innych kwestii związanych z formatowaniem kodu, jedyną prawdziwą zasadą jest spójność i wizualna uprzejmość dla tych, którzy muszą utrzymywać (lub oceniać!) Twój kod.

(cały kod skompilowany na oko)

iandisme
źródło
5

Jedną rzeczą do rozpoznania podczas używania operatora trójskładnikowego jest to, że jest to wyrażenie, a nie instrukcja.

W językach funkcjonalnych, takich jak schemat, rozróżnienie nie istnieje:

(jeśli (> ab) ab)

Warunkowe?: Operator „Nie wydaje się być tak elastyczny jak konstrukcja if / else”

W językach funkcjonalnych tak jest.

Podczas programowania w językach imperatywnych stosuję operator trójskładnikowy w sytuacjach, w których zwykle używałbym wyrażeń (przypisanie, instrukcje warunkowe itp.).

Ken Struys
źródło
5

Chociaż powyższe odpowiedzi są ważne i zgadzam się, że czytelność jest ważna, należy wziąć pod uwagę 2 dalsze kwestie:

  1. W języku C # 6 można mieć metody zawierające wyrażenia.

To sprawia, że ​​użycie trójskładnika jest szczególnie zwięzłe:

string GetDrink(DayOfWeek day) 
   => day == DayOfWeek.Friday
      ? "Beer" : "Tea";
  1. Zachowanie różni się w przypadku niejawnej konwersji typów.

Jeśli masz typów T1i T2które mogą być zarówno niejawnie konwertowane do T, a następnie poniżej robi nie praca:

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

(ponieważ kompilator próbuje określić typ wyrażenia trójskładnikowego i nie ma konwersji między T1a T2).

Z drugiej strony if/elsedziała poniższa wersja:

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

ponieważ T1jest konwertowany Ti tak jestT2

la-yumba
źródło
5

Czasami może to ułatwić odczytanie przypisania wartości bool na pierwszy rzut oka:

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

// Without
button.IsEnabled = !someControl.HasError;
Tyler Pantuso
źródło
4

Jeśli ustawiam wartość i wiem, że zawsze będzie to jeden wiersz kodu, zwykle używam operatora trójargumentowego (warunkowego). Jeśli jest szansa, że ​​mój kod i logika zmienią się w przyszłości, używam if / else, ponieważ jest to bardziej zrozumiałe dla innych programistów.

Może Cię zainteresować ?? operator .

drharris
źródło
4

Zaletą operatora warunkowego jest to, że jest operatorem. Innymi słowy, zwraca wartość. Ponieważ ifjest instrukcją, nie może zwrócić wartości.

Gabe
źródło
4

Zalecałbym ograniczenie używania operatora trójargumentowego (? :) do prostego przypisania pojedynczego wiersza w logice if / else. Coś przypominającego ten wzór:

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

Można go łatwo przekonwertować na:

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

Unikałbym używania operatora trójskładnikowego w sytuacjach, które wymagają if / else if / else, zagnieżdżonej logiki if / else lub if / else branch, która skutkuje obliczeniem wielu wierszy. Zastosowanie operatora trójargumentowego w takich sytuacjach prawdopodobnie spowodowałoby nieczytelny, zagmatwany i niemożliwy do zarządzania kod. Mam nadzieję że to pomoże.

HOCA
źródło
2

Korzystanie z funkcji? operator w np. MS Visual C ++, ale jest to naprawdę rzecz specyficzna dla kompilatora. W niektórych przypadkach kompilator może faktycznie zoptymalizować gałąź warunkową.

darklon
źródło
2

Scenariusz, w którym najczęściej go używam, dotyczy domyślnych wartości, a zwłaszcza zwrotów

return someIndex < maxIndex ? someIndex : maxIndex;

To naprawdę jedyne miejsca, które uważam za miłe, ale dla nich tak.

Chociaż jeśli szukasz wartości logicznej, czasami może to wyglądać na właściwe:

bool hey = whatever < whatever_else ? true : false;

Ponieważ jest tak łatwy do odczytania i zrozumienia, ale ten pomysł zawsze powinien być rzucany na rzecz bardziej oczywistych:

bool hey = (whatever < whatever_else);
Jimmy Hoffa
źródło
2

Jeśli potrzebujesz wielu oddziałów w tym samym stanie, użyj if:

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

Jeśli potrzebujesz wielu gałęzi z różnymi warunkami, to jeśli liczba instrukcji zmieni się w lawinę, będziesz chciał użyć trójskładnika:

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

Możesz także użyć operatora trójskładnikowego podczas inicjalizacji.

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

Robienie tego z if jest bardzo niechlujne:

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

Nie możesz umieścić inicjalizacji wewnątrz if / else, ponieważ zmienia to zakres. Ale odwołania i zmienne const mogą być powiązane tylko podczas inicjalizacji.

Ben Voigt
źródło
2

Operator trójskładnikowy może być zawarty w wartości r, podczas gdy jeśli-to-jeszcze nie może; z drugiej strony if-then-else może wykonywać pętle i inne instrukcje, podczas gdy operator trójskładnikowy może wykonywać tylko (prawdopodobnie void) wartości r.

W podobnej notatce && i || operatory zezwalają na niektóre wzorce wykonywania, które są trudniejsze do zaimplementowania za pomocą if-then-else. Na przykład, jeśli ktoś ma kilka funkcji do wywołania i chce wykonać fragment kodu, jeśli którakolwiek z nich zawiedzie, można to łatwo zrobić za pomocą operatora &&. Zrobienie tego bez tego operatora będzie wymagało nadmiarowego kodu, goto lub dodatkowej zmiennej flagowej.

supercat
źródło
1

W języku C # 7 możesz użyć nowej funkcji odwołań lokalnych, aby uprościć warunkowe przypisanie zmiennych zgodnych z ref. Więc teraz nie tylko możesz:

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

... ale także niezwykle cudowne:

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

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

Że linia kodu przypisuje wartość ado jednej blub c, w zależności od wartości i.



Uwagi
1. r-wartość to prawa strona przypisania, wartość, która jest przypisywana.
2. Wartość-l to lewa strona przypisania, czyli zmienna, która otrzymuje przypisaną wartość.

Glenn Slayden
źródło