ทำไมคำสั่ง (j ++); ห้ามไม่?


169

รหัสต่อไปนี้ผิด (ดูบน ideone ):

public class Test
{
    public static void Main()
    {
        int j = 5;
        (j++);      // if we remove the "(" and ")" then this compiles fine.
    }
}

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

  1. ทำไมโค้ดถึงคอมไพล์เมื่อเราลบวงเล็บ?
  2. ทำไมมันไม่คอมไพล์ด้วยวงเล็บ?
  3. เหตุใด C # จึงถูกออกแบบด้วยวิธีนี้

29
@Servy ผู้คนจำนวนมากที่เกี่ยวข้องกับการออกแบบภาษา C # อยู่ใน SO ดังนั้นนั่นคือเหตุผลที่ฉันถาม มีอะไรผิดปกติหรือ?
user10607

34
ฉันไม่สามารถจินตนาการคำถามในหัวข้อมากกว่า "ทำไมภาษาการเขียนโปรแกรมเฉพาะจัดการกับพฤติกรรมที่เฉพาะเจาะจงในลักษณะนี้ได้อย่างไร"
Mage Xy

20
ตอนนี้เรามีคำตอบของ Eric Lippert บางทีคุณอาจต้องการคิดใหม่เกี่ยวกับคำตอบที่ยอมรับ มันเป็นคริสมาสต์ดังนั้นบางทีคุณอาจตอบรับเร็วเกินไป
Thomas Weller

17
@Servy คุณหมายถึงว่าการตัดสินใจออกแบบไม่เคยบันทึกไว้และไม่เป็นที่รู้จักของคนอื่นอย่างแน่นอนหรือไม่? เขาไม่ได้ขอให้ผู้ใช้ SO คาดเดาเขากำลังขอคำตอบนั่นไม่ได้หมายความว่าเขาขอเดา ไม่ว่าผู้คนจะตอบด้วยการเดาอยู่กับพวกเขาไม่ใช่เขา และเป็น OP ชี้ให้เห็นมีมีคนในกองล้นที่ไม่จริงการทำงานใน C # และทำให้การตัดสินใจเหล่านี้
Rob

5
@Rob: ปัญหาคือว่าใช่ถ้าเหตุผลมีการบันทึกไว้แล้วที่ไหนสักแห่งมันต้อง แต่การเก็งกำไรคือความสนุกและผู้ที่ไม่ต้องการแบ่งปันของตัวเอง? หากคุณพบวิธีที่จะทำให้แน่ใจว่าการทาย (หรือ "การศึกษา") ที่บริสุทธิ์นั้นถูกเลือกสรรอย่างน่าเชื่อถือบอกฉันแล้วเราจะสร้างโชคลาภ
Deduplicator

คำตอบ:


221

ข้อมูลเชิงลึกที่ลึกซึ้งชื่นชม

ฉันจะทำให้ดีที่สุด

ในฐานะที่เป็นคำตอบอื่น ๆ ได้ตั้งข้อสังเกตสิ่งที่เกิดขึ้นที่นี่เป็นคอมไพเลอร์คือการตรวจสอบว่ามีการแสดงออกจะถูกนำมาใช้เป็นคำสั่ง ในหลายภาษา - C, JavaScript และอื่น ๆ อีกมากมาย - มันถูกกฎหมายอย่างสมบูรณ์ที่จะใช้การแสดงออกเป็นคำสั่ง 2 + 2;ถูกกฎหมายในภาษาเหล่านี้แม้ว่านี่จะเป็นคำสั่งที่ไม่มีผลกระทบ นิพจน์บางอย่างมีประโยชน์สำหรับค่าของพวกเขาเท่านั้นนิพจน์บางอย่างมีประโยชน์เฉพาะกับผลข้างเคียงของพวกเขา (เช่นการเรียกใช้วิธีการส่งคืนเป็นโมฆะ) และนิพจน์บางอย่างน่าเสียดายที่มีประโยชน์สำหรับทั้งคู่ (เช่นเพิ่มขึ้น)

จุดเป็นงบที่จะมีเพียงของการแสดงออกจะเกือบแน่นอนข้อผิดพลาดเว้นแต่การแสดงออกเหล่านี้จะคิดว่าโดยทั่วไปแล้วจะเป็นประโยชน์มากสำหรับผลข้างเคียงของพวกเขามากกว่าค่าของพวกเขา นักออกแบบ C # ต้องการหาพื้นกลางโดยให้การแสดงออกที่มักคิดว่าเป็นผลข้างเคียงในขณะที่ไม่อนุญาตให้ผู้ที่คิดว่าเป็นประโยชน์สำหรับค่าของพวกเขา ชุดของนิพจน์ที่ระบุใน C # 1.0 คือการเพิ่มการลดลงการเรียกใช้เมธอดการกำหนดและการอ้างถึงคอนสตรัคชันที่ค่อนข้างขัดแย้ง


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

catch(FooException ex) { new BarException(ex); } 

มันอาจเป็นเรื่องยากที่จะสังเกตเห็นข้อบกพร่องนี้ถ้ารหัสนั้นซับซ้อน


คอมไพเลอร์ทำงานเพื่อตรวจสอบคำสั่งทั้งหมดที่ประกอบด้วยนิพจน์ที่ไม่ได้อยู่ในรายการนั้น โดยเฉพาะอย่างยิ่งนิพจน์ที่วงเล็บถูกระบุเป็นเพียง - นิพจน์ที่วงเล็บ พวกเขาไม่อยู่ในรายการ "อนุญาตให้ใช้เป็นนิพจน์คำสั่ง" ดังนั้นจึงไม่ได้รับอนุญาต

ทั้งหมดนี้ให้บริการตามหลักการออกแบบของภาษา C # หากคุณพิมพ์(x++);คุณอาจจะทำอะไรผิดพลาด นี่อาจเป็นคำที่พิมพ์ผิดM(x++);หรือบางสิ่ง โปรดจำไว้ว่าทัศนคติของทีมคอมไพเลอร์ C # ไม่ได้ " เราสามารถคิดหาวิธีที่จะทำให้งานนี้? " ทัศนคติของทีมคอมไพเลอร์ C # คือ " ถ้าลักษณะรหัสที่เป็นไปได้เช่นความผิดพลาดน่าจะขอแจ้งให้นักพัฒนา " นักพัฒนา C # ชอบทัศนคติเช่นนั้น

ทีนี้ทุกอย่างที่กล่าวมามีกรณีแปลก ๆ อยู่สองสามข้อที่ข้อกำหนดของ C # นั้นบ่งบอกหรือบอกว่าไม่อนุญาตให้ใส่วงเล็บ แต่คอมไพเลอร์ C # ยอมให้พวกเขาอย่างไรก็ตาม ในกรณีส่วนใหญ่ความคลาดเคลื่อนเล็กน้อยระหว่างพฤติกรรมที่ระบุและพฤติกรรมที่ได้รับอนุญาตนั้นไม่เป็นอันตรายอย่างสมบูรณ์ดังนั้นผู้เขียนคอมไพเลอร์ไม่เคยแก้ไขข้อบกพร่องเล็ก ๆ เหล่านี้ คุณสามารถอ่านเกี่ยวกับสิ่งเหล่านี้ได้ที่นี่:

มีความแตกต่างระหว่าง return myVar กับ return (myVar) หรือไม่?


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

1
@ruakh: เนื่องจากนี่เป็นพฤติกรรมที่ควรทำให้หมดกำลังใจจึงไม่จำเป็นต้องมีการแก้ไขที่สะอาดตราบใดที่ยังมีการแก้ไขที่ถูก / ง่าย ซึ่งมี กำหนดให้กับตัวแปรและไม่ใช้ตัวแปรนั้น หากคุณต้องการให้โจ่งแจ้งว่าคุณไม่ได้ใช้งานให้ตั้งรหัสบรรทัดนั้นเป็นขอบเขต ในขณะที่ในทางเทคนิคเราอาจยืนยันว่าสิ่งนี้เปลี่ยนความหมายของรหัส (การมอบหมายการอ้างอิงที่ไร้ประโยชน์ยังคงเป็นการมอบหมายการอ้างอิงที่ไร้ประโยชน์) แต่ในทางปฏิบัติมันไม่น่าเป็นเรื่องสำคัญ ฉันยอมรับว่ามันสร้าง IL แตกต่างกันเล็กน้อย ... แต่ถ้าเรียบเรียงโดยไม่มีการเพิ่มประสิทธิภาพ
Brian

Safari, Firefox และ Chrome ทั้งหมดให้ ReferenceError contineu;เมื่อพิมพ์
Brian McCutchon

2
@McBrainy: คุณพูดถูก ฉันกำลังแก้ไขข้อบกพร่องที่เป็นผลมาจากการสะกดผิด ฉันจะตรวจสอบบันทึกของฉัน ในขณะเดียวกันฉันได้ลบคำแถลงที่ไม่เหมาะสมเพราะมันไม่ใช่จุดที่ใหญ่กว่าที่นี่
Eric Lippert

1
@ ไมค์: ฉันเห็นด้วย อีกสถานการณ์ที่บางครั้งเราเห็นคำสั่งคือกรณีทดสอบเช่นtry { new Foo(null); } catch (ArgumentNullException)...แต่เห็นได้ชัดว่าสถานการณ์เหล่านี้โดยนิยามไม่ใช่รหัสการผลิต ดูเหมือนว่าสมเหตุสมผลที่รหัสดังกล่าวสามารถเขียนเพื่อกำหนดให้กับตัวแปรจำลอง
Eric Lippert

46

ในข้อกำหนดภาษา C #

คำสั่งนิพจน์ใช้เพื่อประเมินนิพจน์ นิพจน์ที่สามารถใช้เป็นคำสั่งรวมถึงการเรียกใช้เมธอดการจัดสรรวัตถุโดยใช้ตัวดำเนินการใหม่การกำหนดโดยใช้ = และตัวดำเนินการกำหนดผสมการเพิ่มและลดการดำเนินการโดยใช้ตัวดำเนินการ ++ และ - และรอนิพจน์

การใส่วงเล็บรอบคำสั่งจะสร้างนิพจน์วงเล็บที่เรียกว่าใหม่ จากข้อกำหนด:

เครื่องหมายวงเล็บประกอบด้วยนิพจน์ที่อยู่ในวงเล็บ ... เครื่องหมายวงเล็บ - นิพจน์ได้รับการประเมินโดยการประเมินค่านิพจน์ภายในวงเล็บ ถ้านิพจน์ภายในวงเล็บแสดงถึงเนมสเปซหรือประเภทข้อผิดพลาดเวลาคอมไพล์จะเกิดขึ้น มิฉะนั้นผลลัพธ์ของวงเล็บ - นิพจน์คือผลลัพธ์ของการประเมินผลของนิพจน์ที่มีอยู่

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


4
@Servy: ถ้าคุณอ่านอย่างใกล้ชิดมันก็มีความเหมาะสมยิ่งกว่านั้นเล็กน้อย ที่เรียกว่า "คำแถลง Expression" นั้นเป็นคำสั่งที่ถูกต้องและข้อมูลจำเพาะนั้นแสดงประเภทของนิพจน์ที่สามารถเป็นคำสั่งที่ถูกต้องได้ อย่างไรก็ตามฉันทำซ้ำจากข้อมูลจำเพาะที่ประเภทของนิพจน์ที่เรียกว่า "นิพจน์วงเล็บเหลี่ยม" ไม่ได้อยู่ในรายการของนิพจน์ที่ถูกต้องซึ่งสามารถใช้เป็นคำสั่งนิพจน์ที่ถูกต้องได้
Frank Bryce

4
สิ่งที่OPไม่ได้ถามเกี่ยวกับการออกแบบภาษา เขาแค่อยากรู้ว่าทำไมมันถึงเป็นข้อผิดพลาด คำตอบคือเพราะมันไม่ได้เป็นคำสั่งที่ถูกต้อง
ลีอันโดร

9
ตกลงฉันไม่ดี คำตอบคือ: เพราะตามข้อกำหนดมันไม่ได้เป็นคำสั่งที่ถูกต้อง ที่นั่นคุณมี: เหตุผลการออกแบบ !
ลีอันโดร

3
คำถามไม่ได้ถามถึงเหตุผลที่ลึกล้ำและแรงผลักดันในการออกแบบว่าทำไมภาษาได้รับการออกแบบมาเพื่อจัดการกับไวยากรณ์นี้ด้วยวิธีนี้ - เขาถามว่าทำไมโค้ดชิ้นหนึ่งคอมไพล์เมื่อโค้ดอื่น (สำหรับผู้เริ่มต้นดูเหมือนว่าควรทำงาน ไม่สามารถรวบรวมได้) โพสต์นี้ตอบว่า
Mage Xy

4
@Servy JohnCarpenter ไม่เพียง แต่พูดว่า "คุณไม่สามารถทำได้" เขาพูดสองสิ่งที่คุณสับสนโดยไม่เหมือนกัน j ++ เป็นคำสั่ง Expression ในขณะที่ (j ++) เป็นเครื่องหมายวงเล็บ ทันใดนั้น OP ทราบถึงความแตกต่างและสิ่งที่พวกเขาเป็น นั่นคือคำตอบที่ดี มันตอบเนื้อหาของคำถามของเขาไม่ใช่ชื่อ ฉันคิดว่าการโต้แย้งจำนวนมากมาจากคำว่า "การออกแบบ" ในชื่อของคำถาม แต่ไม่ต้องตอบโดยนักออกแบบเพียงแค่รวบรวมจากสเปค
think large

19

เพราะวงเล็บรอบi++กำลังสร้าง / กำหนดนิพจน์ .. ในขณะที่ข้อความแสดงข้อผิดพลาดบอกว่า .. นิพจน์อย่างง่าย ๆ ไม่สามารถใช้เป็นคำสั่งได้

ทำไมภาษาจึงถูกออกแบบมาให้เป็นแบบนี้? เพื่อป้องกันข้อบกพร่องมีการแสดงออกที่ทำให้เข้าใจผิดว่าเป็นคำสั่งที่ไม่มีผลข้างเคียงเช่นมีรหัส

int j = 5;
j+1; 

บรรทัดที่สองไม่มีผลกระทบ (แต่คุณอาจไม่สังเกตเห็น) แต่แทนที่จะคอมไพเลอร์ลบมัน (เพราะรหัสไม่จำเป็น). มันขอให้คุณลบมันออกอย่างชัดเจน (ดังนั้นคุณจะได้ทราบหรือข้อผิดพลาด) หรือแก้ไขในกรณีที่คุณลืมพิมพ์บางสิ่งบางอย่าง

แก้ไข :

เพื่อทำให้ส่วนเกี่ยวกับการวงเล็บชัดเจนยิ่งขึ้น .. วงเล็บเหลี่ยมใน c # (นอกเหนือจากการใช้งานอื่น ๆ เช่น cast และ function call) ใช้เพื่อจัดกลุ่มนิพจน์และส่งคืนนิพจน์เดียว (ทำนิพจน์ย่อย)

ในระดับรหัสนั้นอนุญาตให้มี staments เท่านั้น .. ดังนั้น

j++; is a valid statement because it produces side effects

แต่เมื่อใช้เครื่องหมายวงเล็บแสดงว่าคุณเปลี่ยนมันให้เป็นนิพจน์

myTempExpression = (j++)

และนี่

myTempExpression;

ไม่ถูกต้องเพราะคอมไพเลอร์ไม่สามารถมั่นใจได้ว่าการแสดงออกเป็นผลข้างเคียง (ไม่เกิดปัญหาการหยุดชะงัก)


ใช่นั่นคือสิ่งที่ฉันคิดในตอนแรกเช่นกัน แต่นี่จะมีผลข้างเคียง มันทำการเพิ่มjหรือลดค่าตัวแปรใช่ไหม
user10607

1
j++;เป็นคำสั่งที่ถูกต้อง แต่โดยใช้วงเล็บที่คุณพูดlet me take this stement and turn it into an expression.. และการแสดงออกไม่ถูกต้อง ณ จุดนั้นในรหัส
CaldasGSM

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