ประโยชน์ของการใช้ตัวดำเนินการตามเงื่อนไข?: (ternary)


101

ประโยชน์และข้อเสียของตัวดำเนินการคืออะไรเมื่อเทียบกับคำสั่ง if-else มาตรฐาน สิ่งที่ชัดเจนคือ:

เงื่อนไข?: Operator

  • สั้นลงและกระชับมากขึ้นเมื่อต้องจัดการกับการเปรียบเทียบมูลค่าโดยตรงและการมอบหมายงาน
  • ดูเหมือนจะไม่ยืดหยุ่นเท่าโครงสร้าง if / else

มาตรฐานถ้า / อื่น

  • สามารถนำไปใช้กับสถานการณ์อื่น ๆ (เช่นการเรียกใช้ฟังก์ชัน)
  • มักจะยาวโดยไม่จำเป็น

ความสามารถในการอ่านดูเหมือนจะแตกต่างกันไปขึ้นอยู่กับคำแถลง หลังจากที่ได้สัมผัสกับโอเปอเรเตอร์?: เป็นครั้งแรกฉันใช้เวลาพอสมควรในการแยกแยะว่ามันทำงานอย่างไร คุณจะแนะนำให้ใช้ทุกที่ที่เป็นไปได้หรือยึดติดกับ if / else เนื่องจากฉันทำงานกับผู้ที่ไม่ใช่โปรแกรมเมอร์หลายคน


8
คุณมีส่วนสำคัญของมันแล้ว
Byron Whitlock

1
@Nicholas Knight: ฉันเดาว่า OP หมายความว่าคุณทำไม่ได้เช่นSomeCheck() ? DoFirstThing() : DoSecondThing();- คุณต้องใช้นิพจน์เพื่อส่งคืนค่า
ด่านเต๋า

6
ใช้ในที่ที่ชัดเจนติดด้วย if / else ถ้าไม่ใช่ ความชัดเจนของรหัสควรเป็นข้อพิจารณาหลักของคุณ
hollsk

8
คุณเห็นไหม '??' ยัง? อย่างจริงจังถ้าคุณคิดว่าเทอร์นารีเจ๋ง ...
pdr

3
+1 ที่ไม่เรียกมันง่ายๆว่า "the ternary operator" เหมือนที่หลาย ๆ คนทำ แม้ว่าจะเป็นตัวดำเนินการ ternary เพียงตัวเดียว (ตรงข้ามกับยูนารีและไบนารี) ใน C # แต่นั่นไม่ใช่ชื่อของมัน
John M Gant

คำตอบ:


122

โดยพื้นฐานแล้วฉันจะแนะนำให้ใช้ก็ต่อเมื่อคำสั่งที่ได้นั้นสั้นมากและแสดงถึงความกระชับที่เพิ่มขึ้นอย่างมีนัยสำคัญมากกว่าการเทียบเท่า if / else โดยไม่ต้องเสียสละความสามารถในการอ่าน

ตัวอย่างที่ดี:

int result = Check() ? 1 : 0;

ตัวอย่างที่ไม่ดี:

int result = FirstCheck() ? 1 : SecondCheck() ? 1 : ThirdCheck() ? 1 : 0;

5
การโทรที่ดี แต่สำหรับการบันทึกนั่นคือ "ความรัดกุม"
mqp

6
@mquander คุณแน่ใจหรือไม่? merriam-webster.com/dictionary/concise
Byron Whitlock

39
ฉันมักจะเริ่มต้นด้วยคำง่ายๆและทำให้ซับซ้อนขึ้นเมื่อเวลาผ่านไปจนกระทั่งอ่านไม่ออก
Jouke van der Maas

9
ความสามารถในการอ่านในตัวอย่างที่สองสามารถแก้ไขได้อย่างง่ายดายด้วยการจัดรูปแบบที่ดีขึ้น แต่ตามที่ OP แนะนำนั้นจะขึ้นอยู่กับความสามารถในการอ่านและความกระชับเมื่อเทียบกับการใช้คำฟุ่มเฟือย
Nathan Ernst

4
ไม่ใช่ส่วนหนึ่งของคำถามของ OP แต่สิ่งสำคัญที่ควรทราบคือข้อเท็จจริงที่ว่าคุณไม่สามารถเป็นreturnส่วนหนึ่งของผลลัพธ์ของการดำเนินการที่เกี่ยวข้องได้ ตัวอย่างเช่น: check() ? return 1 : return 0;จะไม่ทำงาน แต่return check() ? 1 : 0;จะ สนุกเสมอที่จะค้นหานิสัยใจคอเล็ก ๆ น้อย ๆ เหล่านี้ในการเขียนโปรแกรม
CSS

50

คำตอบอื่น ๆ ครอบคลุมอยู่มาก แต่ "มันคือนิพจน์" ไม่ได้อธิบายว่าทำไมจึงมีประโยชน์ ...

ในภาษาเช่น C ++ และ C # คุณสามารถกำหนดฟิลด์แบบอ่านอย่างเดียวในเครื่อง (ภายในเนื้อความวิธีการ) โดยใช้ สิ่งนี้ไม่สามารถทำได้ด้วยคำสั่ง if / then ทั่วไปเนื่องจากต้องกำหนดค่าของฟิลด์แบบอ่านอย่างเดียวภายในคำสั่งเดียวนั้น:

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

ไม่เหมือนกับ:

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

ในทำนองเดียวกันคุณสามารถฝังนิพจน์ระดับตติยภูมิในโค้ดอื่น ๆ เช่นเดียวกับการทำให้ซอร์สโค้ดมีขนาดกะทัดรัดมากขึ้น (และในบางกรณีก็สามารถอ่านได้มากขึ้น) ยังสามารถทำให้โค้ดเครื่องที่สร้างขึ้นมีขนาดกะทัดรัดและมีประสิทธิภาพมากขึ้น:

MoveCar((shiftKeyDown) ? 10 : 1);

... อาจสร้างรหัสน้อยกว่าที่ต้องเรียกวิธีเดิมซ้ำสอง:

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

แน่นอนว่ามันเป็นรูปแบบที่สะดวกและกระชับกว่าด้วย (พิมพ์น้อยลงซ้ำน้อยลงและสามารถลดโอกาสที่จะเกิดข้อผิดพลาดได้หากคุณต้องทำโค้ดซ้ำกันใน if / else) ในกรณี "รูปแบบทั่วไป" ที่สะอาดเช่นนี้:

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

... มันเร็วกว่าในการอ่าน / แยกวิเคราะห์ / ทำความเข้าใจ (เมื่อคุณเคยชินกับมันแล้ว) กว่าถ้าเทียบเท่ากับ if / else ที่ยืดยาวดังนั้นจึงสามารถช่วยให้คุณ 'grok' โค้ดได้เร็วขึ้น

แน่นอนเพียงเพราะมันมีประโยชน์ไม่ได้หมายความว่ามันเป็นสิ่งที่ดีที่สุดที่จะใช้ในทุกกรณี ฉันขอแนะนำให้ใช้สำหรับโค้ดสั้น ๆ ที่ความหมายชัดเจนเท่านั้น (หรือทำให้ชัดเจนยิ่งขึ้น) โดยใช้?:- ถ้าคุณใช้ในโค้ดที่ซับซ้อนมากขึ้นหรือซ้อนตัวดำเนินการที่เกี่ยวข้องกันมันอาจทำให้โค้ดอ่านยากอย่างน่ากลัว .


@JaminGrey "นั่นไม่ได้หมายความว่าเมื่อสร้างค่าคงที่ค่าคงที่จะตั้งเป็น 10 หรือ 1" คุณหมายถึงไม่หมายถึงการที่? ความคิดเห็นที่ไม่ถูกต้องอาจทำให้เกิดความสับสนกับโปรแกรมเมอร์ C ++ ใหม่มากกว่าปัญหาที่คุณพยายามจะเคลียร์;)
Clonkex

5
สำหรับผู้อ่านในอนาคตที่จะเจอสิ่งนี้โดย " const int speed = (shiftKeyDown)? 10: 1; " นั่นหมายความว่าเมื่อค่าคงที่ถูกสร้างขึ้นครั้งแรกค่าคงที่จะถูกกำหนดเป็น 10 หรือ 1 ไม่ได้หมายความว่าในแต่ละครั้ง มีการเข้าถึงค่าคงที่จะตรวจสอบ (ในกรณีที่โปรแกรมเมอร์ C ++ รุ่นใหม่สับสน)
Jamin Gray

2
... หรือกล่าวอีกนัยหนึ่งconstคือa เป็นค่าคงที่กล่าวคือไม่สามารถเปลี่ยนแปลงได้หลังจากคำสั่งที่ประกาศได้ดำเนินการแล้ว
Jason Williams

1
@JaminGrey. มันควรจะไม่ใช่readonlyเหรอ? ฉันคิดเสมอว่าconstหมายถึง " แก้ไขได้ในเวลารวบรวมและเรียงต่อกันทุกที่ที่ใช้ "
Nolonar

1
@ColinWiseman เป็นตัวอย่างเพื่อแสดงให้เห็นว่า?: สามารถใช้งานได้ ฉันระบุเป็นพิเศษว่าเพียงเพราะคุณทำได้ไม่ได้หมายความว่าสิ่งนั้นจะต้อง "ดีที่สุด" ในบางกรณีเท่านั้น ในการดำเนินการดังกล่าวผู้อ่านควรใช้สมองทุกครั้งที่เจอกรณีที่อาจเป็นประโยชน์กับพวกเขา
Jason Williams

14

ฉันมักจะเลือกตัวดำเนินการ ternary เมื่อฉันมีรหัสซ้ำกันมากเป็นอย่างอื่น

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

ด้วยตัวดำเนินการที่เกี่ยวข้องสิ่งนี้สามารถทำได้ดังต่อไปนี้

answer = compute(a > 0 ? a : -a, b, c, d, e); 

12
ส่วนตัวผมจะทำaVal = a > 0 ? a : -a; answer = compute(aVal,b,c,d,e);โดยเฉพาะอย่างยิ่งถ้าb, c, dและeการรักษาที่จำเป็นมากเกินไป
corsiKa

10
เหตุใดจึงต้องใช้เงื่อนไขในตัวอย่างนี้ เพียงรับ Abs (a) และเรียกใช้ compute () ครั้งเดียว
เถ้า

2
ใช่ฉันไม่ได้สร้างตัวอย่างที่ดีที่สุด :)
Ryan Bright

สำหรับมือใหม่มันดูไม่เทียบเท่า ไม่จำเป็นต้องเป็น answer = คำนวณ (a> 0? a, b, c, d, e: -a, b, c, d, e); เหรอ?
pbreitenbach

@pbreitenbach: ไม่ - มันเป็นเรื่องของลำดับความสำคัญ - อาร์กิวเมนต์แรกcompute(...)คือa > 0 ? a : -1ซึ่งทั้งหมดได้รับการประเมินแยกจากอาร์กิวเมนต์อื่น ๆ ที่คั่นด้วยเครื่องหมายจุลภาค อย่างไรก็ตามน่าเสียดายที่ C ++ ไม่มีสัญกรณ์ที่คำถามของคุณใช้ในการจัดการ "tuples" ของค่าที่คั่นด้วยเครื่องหมายจุลภาคดังนั้นแม้a > 0 ? (a, b, c, d, e) : (-a, b, c, d, e)จะผิดกฎหมายและไม่มีอะไรที่คล้ายกันมากที่ทำงานได้โดยไม่มีการเปลี่ยนแปลงในcomputeตัวมันเอง
Tony Delroy

12

ฉันพบว่ามีประโยชน์อย่างยิ่งเมื่อทำการพัฒนาเว็บหากฉันต้องการตั้งค่าตัวแปรเป็นค่าที่ส่งในคำขอหากมีการกำหนดหรือเป็นค่าเริ่มต้นบางอย่างหากไม่เป็นเช่นนั้น


3
+1 ค่าเริ่มต้นใน web dev เป็นตัวอย่างที่ดีเยี่ยมในการใช้ตัวดำเนินการ ternary
Byron Whitlock

11

การใช้งานที่ยอดเยี่ยมจริงๆคือ:

x = foo ? 1 :
    bar ? 2 :
    baz ? 3 :
          4;

10
ระวังสิ่งนี้ใน PHP ตัวดำเนินการ ternary เชื่อมโยงในทางที่ผิดใน PHP โดยพื้นฐานแล้วถ้าfooเป็นเท็จสิ่งทั้งหมดจะประเมินเป็น 4 โดยไม่ต้องทำการทดสอบอื่น ๆ
Tom Busby

4
@ TomBusby - ว้าว. อีกเหตุผลหนึ่งที่เกลียด PHP ถ้าคุณเป็นคนที่เกลียด PHP อยู่แล้ว
Todd Lehman

6

ตัวดำเนินการตามเงื่อนไขเหมาะสำหรับเงื่อนไขสั้น ๆ เช่นนี้:

varA = boolB ? valC : valD;

ฉันใช้มันเป็นครั้งคราวเพราะใช้เวลาน้อยกว่าในการเขียนบางอย่างในลักษณะนั้น ... น่าเสียดายที่บางครั้งนักพัฒนารายอื่นที่เรียกดูโค้ดของคุณอาจพลาดการแยกสาขานี้ไป นอกจากนี้รหัสมักจะไม่สั้นดังนั้นฉันมักจะช่วยให้อ่านง่ายโดยใส่? และ: ในบรรทัดแยกกันเช่นนี้:

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

อย่างไรก็ตามข้อดีอย่างมากในการใช้บล็อก if / else (และทำไมฉันถึงชอบมากกว่านั้น) คือการเข้ามาในภายหลังง่ายกว่าและเพิ่มตรรกะเพิ่มเติมให้กับสาขา

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

หรือเพิ่มเงื่อนไขอื่น:

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

ท้ายที่สุดแล้วตอนนี้ก็เป็นเรื่องของความสะดวกสบายสำหรับคุณ (ใช้สั้นกว่า:?) เทียบกับความสะดวกสำหรับคุณ (และคนอื่น ๆ ) ในภายหลัง เป็นการเรียกร้องการตัดสิน ... แต่เช่นเดียวกับปัญหาการจัดรูปแบบโค้ดอื่น ๆ กฎที่แท้จริงเพียงข้อเดียวคือต้องมีความสอดคล้องและแสดงความสุภาพต่อผู้ที่ต้องรักษา (หรือให้คะแนน!) รหัสของคุณ

(เขียนโค้ดทั้งหมดด้วยตา)


5

สิ่งหนึ่งที่ต้องรับรู้เมื่อใช้ตัวดำเนินการ ternary ว่ามันเป็นนิพจน์ไม่ใช่คำสั่ง

ในภาษาที่ใช้งานได้เช่นแบบแผนไม่มีความแตกต่าง:

(ถ้า (> ab) ab)

เงื่อนไข?: ตัวดำเนินการ "ดูเหมือนจะไม่ยืดหยุ่นเท่าโครงสร้าง if / else"

ในภาษาที่ใช้งานได้คือ

เมื่อเขียนโปรแกรมในภาษาที่จำเป็นฉันใช้ตัวดำเนินการตามเงื่อนไขในสถานการณ์ที่โดยปกติฉันจะใช้นิพจน์ (การกำหนดคำสั่งเงื่อนไข ฯลฯ )


5

แม้ว่าคำตอบข้างต้นจะถูกต้องและฉันยอมรับว่าการอ่านมีความสำคัญ แต่มีอีก 2 ประเด็นที่ต้องพิจารณา:

  1. ใน C # 6 คุณสามารถมีวิธีการแสดงออกทางร่างกาย

สิ่งนี้ทำให้การใช้ ternary รัดกุมเป็นพิเศษ:

string GetDrink(DayOfWeek day) 
   => day == DayOfWeek.Friday
      ? "Beer" : "Tea";
  1. พฤติกรรมแตกต่างกันเมื่อพูดถึงการแปลงประเภทโดยนัย

หากคุณมีประเภทT1และT2ทั้งสองอย่างสามารถแปลงเป็นโดยปริยายTด้านล่างจะไม่ทำงาน:

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

(เนื่องจากคอมไพลเลอร์พยายามกำหนดประเภทของนิพจน์ด้านท้ายและไม่มีการแปลงระหว่างT1และT2)

ในทางกลับกันif/elseเวอร์ชันด้านล่างใช้งานได้:

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

เพราะT1ถูกแปลงเป็นTและเป็นเช่นนั้นT2


5

บางครั้งอาจทำให้การกำหนดค่าบูลอ่านง่ายขึ้นในตอนแรก:

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

// Without
button.IsEnabled = !someControl.HasError;

4

ถ้าฉันตั้งค่าและฉันรู้ว่ามันจะเป็นโค้ดบรรทัดเดียวเสมอฉันมักจะใช้ตัวดำเนินการ ternary (ตามเงื่อนไข) หากมีโอกาสที่รหัสและตรรกะของฉันจะเปลี่ยนไปในอนาคตฉันใช้ if / else เพราะมันชัดเจนกว่าสำหรับโปรแกรมเมอร์คนอื่น ๆ

ที่น่าสนใจสำหรับคุณอาจเป็น?? ผู้ประกอบการ


4

ข้อดีของตัวดำเนินการตามเงื่อนไขคือเป็นตัวดำเนินการ กล่าวอีกนัยหนึ่งคือส่งคืนค่า เนื่องจากifเป็นคำสั่งจึงไม่สามารถส่งคืนค่าได้


4

ฉันขอแนะนำให้ จำกัด การใช้ตัวดำเนินการ ternary (? :) เฉพาะการกำหนดบรรทัดเดียวแบบง่าย ๆ ถ้า / else logic สิ่งที่คล้ายกับรูปแบบนี้:

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

สามารถแปลงเป็น:

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

ฉันจะหลีกเลี่ยงการใช้ตัวดำเนินการ ternary ในสถานการณ์ที่ต้องใช้ if / else if / else ซ้อนกัน if / else หรือ if / else branch logic ที่ส่งผลให้มีการประเมินหลายบรรทัด การใช้ตัวดำเนินการ ternary ในสถานการณ์เหล่านี้อาจส่งผลให้โค้ดไม่สามารถอ่านได้สับสนและไม่สามารถจัดการได้ หวังว่านี่จะช่วยได้


2

มีประโยชน์ด้านประสิทธิภาพของการใช้ปุ่ม? ตัวดำเนินการเช่น MS Visual C ++ แต่นี่เป็นสิ่งเฉพาะของคอมไพเลอร์ คอมไพเลอร์สามารถปรับให้เหมาะสมที่สุดสำหรับสาขาเงื่อนไขในบางกรณี


2

สถานการณ์ที่ฉันพบว่าตัวเองใช้บ่อยที่สุดคือการกำหนดค่าเริ่มต้นและโดยเฉพาะอย่างยิ่งในการคืน

return someIndex < maxIndex ? someIndex : maxIndex;

นี่เป็นสถานที่เดียวที่ฉันคิดว่าดี แต่สำหรับพวกเขาฉันทำ

แม้ว่าคุณกำลังมองหาบูลีนบางครั้งอาจดูเหมือนเป็นสิ่งที่ควรทำ:

bool hey = whatever < whatever_else ? true : false;

เพราะมันง่ายมากที่จะอ่านและเข้าใจ แต่ควรโยนความคิดนั้นออกไปเพื่อให้ชัดเจนยิ่งขึ้น:

bool hey = (whatever < whatever_else);

2

หากคุณต้องการหลายสาขาในเงื่อนไขเดียวกันให้ใช้ if:

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

หากคุณต้องการหลายสาขาที่มีเงื่อนไขที่แตกต่างกันหากการนับคำสั่งเป็นก้อนหิมะคุณจะต้องใช้ ternary:

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

นอกจากนี้คุณสามารถใช้ตัวดำเนินการ ternary ในการเริ่มต้น

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

การทำเช่นนั้นหากยุ่งมาก:

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

คุณไม่สามารถเริ่มต้นใน if / else ได้เนื่องจากจะเปลี่ยนขอบเขต แต่การอ้างอิงและตัวแปร const สามารถผูกไว้ที่การเริ่มต้นเท่านั้น


2

สามารถรวมตัวดำเนินการ ternary ไว้ใน rvalue ได้ในขณะที่ if-then-else ไม่สามารถทำได้ ในทางกลับกัน if-then-else สามารถรันลูปและคำสั่งอื่น ๆ ได้ในขณะที่ตัวดำเนินการ ternary สามารถดำเนินการได้เฉพาะค่า rvalues ​​(อาจเป็นโมฆะ)

ในบันทึกที่เกี่ยวข้องเครื่องหมาย && และ || ตัวดำเนินการอนุญาตให้ใช้รูปแบบการดำเนินการบางอย่างซึ่งยากที่จะใช้กับ if-then-else ตัวอย่างเช่นหากมีฟังก์ชันหลายอย่างในการเรียกใช้และต้องการเรียกใช้โค้ดหากมีข้อผิดพลาดก็สามารถทำได้โดยใช้ตัวดำเนินการ && การทำโดยไม่มีตัวดำเนินการนั้นจะต้องใช้โค้ดซ้ำซ้อน goto หรือตัวแปรแฟล็กเพิ่มเติม


1

ด้วยC # 7คุณสามารถใช้คุณลักษณะอ้างอิงใหม่เพื่อลดความซับซ้อนของการกำหนดตัวแปรที่เข้ากันได้กับการอ้างอิงตามเงื่อนไข ตอนนี้คุณไม่เพียง แต่ทำได้:

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

... แต่ยังยอดเยี่ยมมาก:

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

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

บรรทัดของรหัสที่กำหนดค่าของaอย่างใดอย่างหนึ่งbหรือขึ้นอยู่กับค่าของci



หมายเหตุ
1. r-valueคือด้านขวามือของการกำหนดค่าที่ได้รับมอบหมาย
2. l-valueคือด้านซ้ายมือของการกำหนดตัวแปรที่รับค่าที่กำหนด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.