ข้อ จำกัด คำสั่งสวิตช์ C # - เพราะเหตุใด


141

เมื่อเขียนคำสั่ง switch ดูเหมือนจะมีข้อ จำกัด สองประการเกี่ยวกับสิ่งที่คุณสามารถเปิดได้ในคำสั่ง case

ตัวอย่างเช่น (และใช่ฉันรู้ว่าถ้าคุณกำลังทำสิ่งนี้มันอาจหมายถึงสถาปัตยกรรมเชิงวัตถุ (OO) ของคุณคือไม่แน่นอน - นี่เป็นเพียงตัวอย่างที่ได้วางแผนไว้!),

  Type t = typeof(int);

  switch (t) {

    case typeof(int):
      Console.WriteLine("int!");
      break;

    case typeof(string):
      Console.WriteLine("string!");
      break;

    default:
      Console.WriteLine("unknown!");
      break;
  }

นี่คือคำสั่ง switch () ล้มเหลวด้วย 'ค่าประเภทหนึ่งที่คาดหมาย' และคำสั่งกรณีล้มเหลวด้วย 'คาดว่าจะมีค่าคงที่'

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



ตัวเลือกสำหรับการเปิดตัวในรูปแบบหนึ่งคือการใช้TypeCode Enum
Erik Philips

เพียงแค่สร้าง ENUM และใช้ NameOf ในตัวพิมพ์สลับ มันจะทำงานเป็นค่าคงที่กับตัวแปรแบบไดนามิก
Vaibhav.Inspired

คำตอบ:


99

นี่คือโพสต์ดั้งเดิมของฉันซึ่งจุดประกายการอภิปรายบางอย่าง ... เพราะมันผิด :

คำสั่ง switch ไม่เหมือนกับคำสั่ง if-else ที่มีขนาดใหญ่ แต่ละกรณีจะต้องไม่ซ้ำกันและประเมินผลแบบคงที่ คำสั่ง switch จะทำการแยกเวลาให้คงที่ไม่ว่าคุณจะมีกี่กรณี คำสั่ง if-else ประเมินแต่ละเงื่อนไขจนกว่าจะพบเงื่อนไขที่เป็นจริง


อันที่จริงคำสั่งเปลี่ยน C # ไม่ใช่สาขาเวลาคงที่เสมอไป

ในบางกรณีคอมไพเลอร์จะใช้คำสั่ง CIL switch ซึ่งแน่นอนว่าเป็นช่วงเวลาคงที่โดยใช้ตารางการกระโดด อย่างไรก็ตามในกรณีกระจัดกระจายตามที่อีวานแฮมิลตันชี้คอมไพเลอร์อาจสร้างสิ่งอื่นทั้งหมด

จริง ๆ แล้วค่อนข้างง่ายต่อการตรวจสอบโดยการเขียนคำสั่ง C # สวิตช์ต่าง ๆ บ้างกระจัดกระจายบ้างหนาแน่นและดู CIL ที่ได้ด้วยเครื่องมือ ildasm.exe


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

โปรดดูโพสต์ของฉันด้านล่างที่ฉันแสดงในความเห็นของฉันในที่สุดว่างบเปลี่ยนเป็นสาขาเวลาคงที่
Brian Ensink

ขอบคุณมากสำหรับคำตอบของคุณ Brian โปรดดูคำตอบของ Ivan Hamilton (( 48259 ) [ beta.stackoverflow.com/questions/44905/#48259] ) กล่าวโดยย่อ: คุณกำลังพูดถึงswitch คำสั่ง (ของ CIL) ซึ่งไม่เหมือนกับswitchคำสั่ง C #
mweerden

ฉันไม่เชื่อว่าคอมไพเลอร์สร้างการแยกเวลาคงที่เมื่อเปลี่ยนสตริง
Drew Noakes

สิ่งนี้ยังคงใช้กับรูปแบบการจับคู่ในคำสั่ง switch case ใน C # 7.0 ได้หรือไม่?
B. Darren Olson

114

เป็นเรื่องสำคัญที่จะไม่สร้างความสับสนให้กับข้อความสั่งสวิตช์ C # ด้วยคำสั่งสวิตช์ CIL

สวิตช์ CIL เป็นตารางการกระโดดที่ต้องการดัชนีในชุดของที่อยู่การกระโดด

สิ่งนี้มีประโยชน์ก็ต่อเมื่อกรณีของสวิตช์ C # อยู่ติดกัน:

case 3: blah; break;
case 4: blah; break;
case 5: blah; break;

แต่ใช้น้อยถ้าพวกเขาไม่ได้:

case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;

(คุณต้องมีขนาดโต๊ะประมาณ 3000 รายการโดยใช้เพียง 3 ช่องเท่านั้น)

ด้วยนิพจน์ที่ไม่ติดกันคอมไพเลอร์อาจเริ่มทำการตรวจสอบเชิงเส้น if-else-if-else

คอมไพเลอร์อาจเริ่มต้นด้วยการค้นหาต้นไม้แบบไบนารีและสุดท้ายถ้า-else-if-else รายการสองสามรายการสุดท้าย

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

นี่เต็มไปด้วย "mays" & "mights" และขึ้นอยู่กับคอมไพเลอร์ (อาจแตกต่างกับโมโนหรือโรเตอร์)

ฉันจำลองผลลัพธ์ของคุณบนเครื่องโดยใช้เคสที่อยู่ติดกัน:

เวลาทั้งหมดในการเรียกใช้สวิตช์ 10 ทาง, 10,000 รอบ (ms): 25.1383
เวลาโดยประมาณต่อสวิตช์ 10 ทาง (ms): 0.00251383

เวลาทั้งหมดในการเรียกใช้สวิตช์ 50 ทาง, 10,000 รอบ (ms): 26.593
เวลาโดยประมาณต่อสวิตช์ 50 ทาง (ms): 0.0026593

เวลาทั้งหมดในการเรียกใช้สวิตช์ 5000 ทาง, 10,000 รอบ (ms): 23.7094
เวลาโดยประมาณต่อสวิตช์ 5000 ทาง (มิลลิวินาที): 0.00237094

เวลาทั้งหมดในการเรียกใช้สวิตช์ 50000 วิธี, 10,000 ซ้ำ (มิลลิวินาที): 20.0933
เวลาโดยประมาณต่อสวิตช์ 50000 ทาง (มิลลิวินาที): 0.00200933

จากนั้นฉันก็ใช้นิพจน์ตัวพิมพ์เล็กและตัวพิมพ์เล็ก:

เวลาทั้งหมดในการเรียกใช้สวิตช์ 10 ทาง, 10,000 รอบ (ms): 19.6189
เวลาโดยประมาณต่อสวิตช์ 10 ทาง (ms): 0.00196189

เวลาทั้งหมดในการเรียกใช้สวิตช์ 500 ทาง, 10,000 รอบ (ms): 19.1664
เวลาโดยประมาณต่อสวิตช์ 500 ทาง (ms): 0.00191664

เวลาทั้งหมดในการเรียกใช้สวิตช์ 5000 ทาง, 10,000 รอบ (ms): 19.5871
เวลาโดยประมาณต่อสวิตช์ 5000 ทาง (ms): 0.00195871

คำสั่งสับเปลี่ยนกรณีขนาด 50,000 คำที่ไม่อยู่ติดกันจะไม่รวบรวม
"นิพจน์ยาวเกินไปหรือซับซ้อนเกินกว่าที่จะคอมไพล์ใกล้กับ 'ConsoleApplication1.Program.Main (string [])'

มีอะไรตลกที่นี่คือการค้นหาแบบต้นไม้ไบนารีจะเร็วกว่าการเรียนการสอนสวิตช์ CIL เล็กน้อย (อาจจะไม่เชิงสถิติ)

ไบรอันคุณใช้คำว่า " ค่าคงที่ " " ซึ่งมีความหมายที่ชัดเจนมากจากมุมมองของทฤษฎีความซับซ้อนในการคำนวณ ในขณะที่ตัวอย่างจำนวนเต็มที่อยู่ติดกันแบบง่าย ๆ อาจสร้าง CIL ซึ่งถือว่าเป็น O (1) (ค่าคงที่) แต่ตัวอย่างกระจัดกระจายคือ O (log n) (ลอการิทึม) ตัวอย่างกลุ่มที่อยู่ตรงกลางและตัวอย่างเล็ก ๆ คือ O (n) )

สิ่งนี้ไม่ได้ระบุถึงสถานการณ์ของ String ซึ่งGeneric.Dictionary<string,int32>อาจสร้างแบบคงที่และจะได้รับค่าโสหุ้ยที่แน่นอนเมื่อใช้ครั้งแรก Generic.Dictionaryผลการดำเนินงานที่นี่จะขึ้นอยู่กับประสิทธิภาพการทำงานของ

หากคุณตรวจสอบข้อกำหนดภาษา C # (ไม่ใช่ข้อมูลจำเพาะ CIL) คุณจะพบ "15.7.2 คำสั่ง switch" ไม่พูดถึง "เวลาคงที่" หรือการติดตั้งพื้นฐานนั้นยังใช้คำสั่ง CIL switch ด้วย (โปรดระวังอย่างมาก สิ่งต่าง ๆ )

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


แน่นอนเวลาเหล่านี้จะขึ้นอยู่กับเครื่องจักรและเงื่อนไข ฉันจะไม่ใส่ใจกับการทดสอบเวลาเหล่านี้ระยะเวลาไมโครวินาทีที่เรากำลังพูดถึงนั้นถูกแคระด้วยรหัส "ของจริง" ใด ๆ ที่กำลังทำงานอยู่ (และคุณต้องรวม "รหัสจริง" ไว้ไม่เช่นนั้นคอมไพเลอร์จะปรับสาขาออก) หรือ กระวนกระวายใจในระบบ คำตอบของฉันนั้นขึ้นอยู่กับการใช้IL DASMเพื่อตรวจสอบ CIL ที่สร้างโดยคอมไพเลอร์ C # แน่นอนว่านี่ไม่ใช่ขั้นตอนสุดท้ายเนื่องจากคำสั่งจริงที่ JIT สร้างขึ้นนั้นจะสร้างขึ้น

ฉันได้ตรวจสอบคำสั่ง CPU ขั้นสุดท้ายที่ดำเนินการจริงในเครื่อง x86 ของฉันและสามารถยืนยันสวิตช์ตั้งค่าที่อยู่ติดกันอย่างง่าย ๆ ที่ทำสิ่งต่าง ๆ เช่น:

  jmp     ds:300025F0[eax*4]

ตำแหน่งที่การค้นหาแบบต้นไม้ไบนารีเต็มไปด้วย:

  cmp     ebx, 79Eh
  jg      3000352B
  cmp     ebx, 654h
  jg      300032BB
  
  cmp     ebx, 0F82h
  jz      30005EEE

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

เป็นการยากที่จะคำนวณเวลาให้ถูกต้องด้วยการทำงานเล็ก ๆ เราไม่ได้แชร์รหัสหรือขั้นตอนการทดสอบ ฉันไม่เห็นว่าทำไมเวลาของเขาควรเพิ่มขึ้นสำหรับกรณีที่อยู่ติดกัน เหมืองของฉันเร็วขึ้น 10 เท่าดังนั้นสภาพแวดล้อมและรหัสทดสอบอาจแตกต่างกันอย่างมาก
Ivan Hamilton

23

เหตุผลแรกที่นึกถึงคือประวัติศาสตร์ :

เนื่องจากโปรแกรมเมอร์ C, C ++ และ Java ส่วนใหญ่ไม่คุ้นเคยกับการมีเสรีภาพดังกล่าวจึงไม่ต้องการมัน

เหตุผลที่ถูกต้องมากกว่าคือความซับซ้อนของภาษาจะเพิ่มขึ้น :

ก่อนอื่นควรเปรียบเทียบวัตถุกับ.Equals()หรือกับตัว==ดำเนินการหรือไม่ ทั้งสองถูกต้องในบางกรณี เราควรจะแนะนำซินแท็กซ์ใหม่เพื่อทำสิ่งนี้หรือไม่? เราควรอนุญาตให้โปรแกรมเมอร์แนะนำวิธีการเปรียบเทียบของตนเองหรือไม่?

นอกจากนี้การอนุญาตให้เปิดใช้งานวัตถุจะทำให้ข้อสมมติฐานพื้นฐานเกี่ยวกับคำสั่งสวิตช์เปลี่ยนไป มีกฎสองข้อที่ควบคุมคำสั่ง switch ที่คอมไพเลอร์จะไม่สามารถบังคับใช้ถ้าวัตถุได้รับอนุญาตให้เปิด (ดูข้อกำหนดภาษา C # เวอร์ชัน 3.0 , ,8.7.2):

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

ลองพิจารณาตัวอย่างรหัสนี้ในกรณีสมมุติว่าอนุญาตให้ใช้ค่าตัวพิมพ์ใหญ่ - ไม่ใช่:

void DoIt()
{
    String foo = "bar";
    Switch(foo, foo);
}

void Switch(String val1, String val2)
{
    switch ("bar")
    {
        // The compiler will not know that val1 and val2 are not distinct
        case val1:
            // Is this case block selected?
            break;
        case val2:
            // Or this one?
            break;
        case "bar":
            // Or perhaps this one?
            break;
    }
}

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

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


2
ของโน้ตบนสวิตช์การเรียงลำดับใหม่ การล้มผ่านนั้นถูกกฎหมายหากกรณีไม่มีรหัส เช่นกรณีที่ 1: กรณีที่ 2: Console.WriteLine ("Hi"); หยุดพัก;
Joel McBeth

10

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


1
Select Caseen VB เป็นอย่างมากที่มีความยืดหยุ่นและประหยัดเวลาสุด ฉันคิดถึงมันมาก
Eduardo Molteni

@EduardoMolteni เปลี่ยนเป็น F # แล้ว มันทำให้สวิตช์ของ Pascal และ VB ดูเหมือนเป็นเด็กงี่เง่าเมื่อเปรียบเทียบ
Luaan

10

ข้อ จำกัด เหล่านี้ส่วนใหญ่เกิดขึ้นเนื่องจากนักออกแบบภาษา เหตุผลพื้นฐานอาจเข้ากันได้กับประวัติย่อ, อุดมการณ์หรือลดความซับซ้อนของการออกแบบคอมไพเลอร์

คอมไพเลอร์อาจ (และไม่) เลือกที่:

  • สร้างคำสั่ง if-else ขนาดใหญ่
  • ใช้คำสั่งสวิตช์ MSIL (ตารางกระโดด)
  • สร้าง Generic.Dictionary <string, int32>, เติมมันในการใช้งานครั้งแรก, และเรียก Generic.Dictionary <> :: TryGetValue () สำหรับดัชนีเพื่อส่งไปยังคำสั่งสวิตช์ MSIL (ตารางกระโดด)
  • ใช้การรวมกันของการข้าม "สลับ" if-elses & MSIL

คำสั่ง switch ไม่ใช่สาขาเวลาคงที่ คอมไพเลอร์อาจพบทางลัด (ใช้ hash buckets, ฯลฯ ) แต่กรณีที่ซับซ้อนจะสร้างรหัส MSIL ที่ซับซ้อนมากขึ้นด้วยบางกรณีที่แตกแขนงออกมาเร็วกว่าที่อื่น

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

สำหรับความต้องการใช้นิพจน์แบบสแตติกกรณี ... การเพิ่มประสิทธิภาพบางอย่าง (การแฮชการแคช ฯลฯ ) จะไม่สามารถใช้ได้หากนิพจน์กรณีไม่ได้กำหนดไว้ล่วงหน้า แต่เราได้เห็นแล้วว่าบางครั้งคอมไพเลอร์ก็เลือกถนนแบบ if-else-if-else แบบเรียบง่ายอยู่แล้ว ...

แก้ไข: lomaxx - ความเข้าใจของคุณเกี่ยวกับตัวดำเนินการ "typeof" ไม่ถูกต้อง ตัวดำเนินการ "typeof" ถูกใช้เพื่อรับวัตถุ System.Type สำหรับชนิด (ไม่มีส่วนเกี่ยวข้องกับ supertypes หรืออินเทอร์เฟซ) การตรวจสอบความเข้ากันได้ในขณะใช้งานของวัตถุที่มีประเภทที่กำหนดคืองานของ "is" การใช้ "typeof" ที่นี่เพื่อแสดงวัตถุไม่เกี่ยวข้อง


6

ในขณะที่ในหัวข้อตามที่เจฟฟ์แอดงบเปลี่ยนเป็นความโหดร้ายการเขียนโปรแกรม ใช้พวกเขาเท่าที่จำเป็น

คุณมักจะสามารถทำงานเดียวกันให้สำเร็จโดยใช้ตาราง ตัวอย่างเช่น:

var table = new Dictionary<Type, string>()
{
   { typeof(int), "it's an int!" }
   { typeof(string), "it's a string!" }
};

Type someType = typeof(int);
Console.WriteLine(table[someType]);

7
คุณกำลังพูดอย่างจริงจังกับใครบางคนที่โพสต์ข้อมือ Twitter โดยไม่มีหลักฐาน? ลิงก์อย่างน้อยไปยังแหล่งข้อมูลที่เชื่อถือได้
Ivan Hamilton

4
มันมาจากแหล่งที่เชื่อถือได้; โพสต์ Twitter ในคำถามมาจาก Jeff Atwood ผู้เขียนเว็บไซต์ที่คุณกำลังดู :-) Jeff มีบล็อกโพสต์จำนวนหนึ่งในหัวข้อนี้หากคุณสงสัย
Judah Gabriel Himango

ฉันเชื่อว่าเป็น BS ทั้งหมด - ไม่ว่าจะเป็น Jeff Atwood หรือไม่ก็ตาม มันตลกดีที่คำสั่ง switch ให้ยืมกับการจัดการเครื่องจักรของรัฐและตัวอย่างอื่น ๆ ของการเปลี่ยนการไหลของรหัสตามค่าของenumประเภท มันไม่ใช่เรื่องบังเอิญที่ intellisense จะใส่คำสั่ง switch โดยอัตโนมัติเมื่อคุณเปิดตัวแปรenumประเภทหนึ่ง
Jonathon Reinhart

@ JonathonReinhart ใช่ฉันคิดว่านั่นเป็นประเด็น - มีวิธีที่ดีกว่าในการจัดการรหัส polymorphic กว่าการใช้switchคำสั่ง เขาไม่ได้บอกว่าคุณไม่ควรเขียนกลไกสถานะเพียงแค่คุณทำสิ่งเดียวกันโดยใช้ประเภทเฉพาะที่ดี แน่นอนว่านี่เป็นภาษาที่ง่ายกว่ามากเช่น F # ซึ่งมีประเภทที่สามารถครอบคลุมสถานะที่ค่อนข้างซับซ้อนได้อย่างง่ายดาย สำหรับตัวอย่างของคุณคุณสามารถใช้สหภาพที่มีการแบ่งแยกซึ่งสถานะนั้นเป็นส่วนหนึ่งของประเภทและแทนที่switchด้วยการจับคู่รูปแบบ หรือใช้อินเตอร์เฟสเช่น
Luaan

คำตอบ / คำถามเก่า แต่ฉันจะคิดว่า (แก้ไขฉันถ้าฉันผิด) Dictionaryจะช้ากว่าswitchคำสั่งที่ปรับให้เหมาะสมที่สุด...
พอล

6

ฉันไม่เห็นเหตุผลใด ๆ ที่คำสั่ง switch จะต้องมีการรวบรวมไว้ที่การวิเคราะห์แบบคงที่เท่านั้น

เป็นจริงไม่จำเป็นต้องมีและหลายภาษาใช้คำสั่งสวิตช์แบบไดนามิกในความเป็นจริง ซึ่งหมายความว่าอย่างไรก็ตามการเรียงลำดับคำสั่ง "เคส" ใหม่สามารถเปลี่ยนพฤติกรรมของรหัสได้

มีข้อมูลที่น่าสนใจบางอย่างที่อยู่เบื้องหลังการตัดสินใจออกแบบที่เปลี่ยนเป็น "สวิตช์" ในที่นี้: เหตุใดคำสั่งสวิตช์ C # ที่ออกแบบมาเพื่อไม่ให้ล้มลง แต่ยังต้องการการหยุดพัก

การอนุญาตนิพจน์กรณีแบบไดนามิกสามารถนำไปสู่ความประหลาดเช่นรหัส PHP นี้:

switch (true) {
    case a == 5:
        ...
        break;
    case b == 10:
        ...
        break;
}

ซึ่งตรงไปตรงมาควรใช้if-elseคำสั่ง


1
นั่นคือสิ่งที่ฉันชอบเกี่ยวกับ PHP (ตอนนี้เมื่อฉันเปลี่ยนเป็น C #) มันเป็นอิสระ เมื่อมีอิสระในการเขียนโค้ดที่ไม่ดี แต่นี่คือสิ่งที่ฉันคิดถึงใน C #
silkfire

5

ในที่สุด Microsoft ก็ได้ยินคุณ!

ตอนนี้ด้วย C # 7 คุณสามารถ:

switch(shape)
{
case Circle c:
    WriteLine($"circle with radius {c.Radius}");
    break;
case Rectangle s when (s.Length == s.Height):
    WriteLine($"{s.Length} x {s.Height} square");
    break;
case Rectangle r:
    WriteLine($"{r.Length} x {r.Height} rectangle");
    break;
default:
    WriteLine("<unknown shape>");
    break;
case null:
    throw new ArgumentNullException(nameof(shape));
}

3

นี่ไม่ใช่เหตุผลว่าทำไม แต่ส่วนข้อกำหนดคุณสมบัติ C # 8.7.2 ระบุสิ่งต่อไปนี้:

ชนิดการควบคุมของคำสั่ง switch ถูกสร้างขึ้นโดย expression ของสวิตช์ หากชนิดของนิพจน์สวิตช์เป็น sbyte, byte, short, ushort, int, uint, long, ulong, char, string หรือ enum-type นั่นคือประเภทการปกครองของคำสั่ง switch มิฉะนั้นต้องมีการแปลงโดยนัยหนึ่งรายการที่ผู้ใช้กำหนด (§6.4) จากชนิดของนิพจน์สวิตช์เป็นหนึ่งในประเภทที่เป็นไปได้ดังต่อไปนี้: sbyte, ไบต์, สั้น, ushort, int, uint, ยาว, ulong, char, string . หากไม่มีการแปลงโดยนัยดังกล่าวอยู่หรือหากมีการแปลงโดยนัยดังกล่าวมากกว่าหนึ่งรายการจะเกิดข้อผิดพลาดในการรวบรวมเวลา

ข้อมูลจำเพาะ C # 3.0 อยู่ที่: http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc


3

คำตอบของยูดาห์ด้านบนทำให้ฉันมีความคิด คุณสามารถ "ปลอม" พฤติกรรมการสลับของ OP ด้านบนโดยใช้Dictionary<Type, Func<T>:

Dictionary<Type, Func<object, string,  string>> typeTable = new Dictionary<Type, Func<object, string, string>>();
typeTable.Add(typeof(int), (o, s) =>
                    {
                        return string.Format("{0}: {1}", s, o.ToString());
                    });

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


0

ฉันคิดว่าไม่มีเหตุผลพื้นฐานที่ผู้แปลไม่สามารถแปลคำสั่ง switch ของคุณเป็น:

if (t == typeof(int))
{
...
}
elseif (t == typeof(string))
{
...
}
...

แต่นั่นก็ไม่ได้รับมาก

คำสั่ง case เกี่ยวกับ integral type อนุญาตให้คอมไพเลอร์สร้างจำนวนของการปรับให้เหมาะสม:

  1. ไม่มีการทำซ้ำ (เว้นแต่คุณจะทำซ้ำป้ายกำกับกรณีซึ่งคอมไพเลอร์ตรวจพบ) ในตัวอย่างของคุณ t สามารถจับคู่ได้หลายประเภทเนื่องจากการสืบทอด การแข่งขันนัดแรกควรมีการดำเนินการหรือไม่ พวกเขาทุกคน?

  2. คอมไพเลอร์สามารถเลือกที่จะใช้คำสั่งสลับกับประเภทหนึ่งโดยตารางกระโดดเพื่อหลีกเลี่ยงการเปรียบเทียบทั้งหมด หากคุณกำลังสลับกับการแจงนับที่มีค่าจำนวนเต็ม 0 ถึง 100 มันจะสร้างอาร์เรย์ที่มี 100 พอยน์เตอร์หนึ่งตัวสำหรับแต่ละคำสั่งสวิตช์ ที่รันไทม์มันจะค้นหาที่อยู่จากอาเรย์โดยยึดตามค่าจำนวนเต็มที่เปิดอยู่ สิ่งนี้ทำให้ประสิทธิภาพรันไทม์ดีกว่าการเปรียบเทียบ 100 ครั้ง


1
ซับซ้อนสิ่งสำคัญที่จะต้องทราบที่นี่เป็นที่.NETรุ่นหน่วยความจำมีการค้ำประกันที่แข็งแกร่งบางอย่างที่ทำให้ pseudocode ของคุณได้อย่างแม่นยำเทียบเท่ากับ (สมมุติที่ไม่ถูกต้องC # ) switch (t) { case typeof(int): ... }เพราะการแปลของคุณหมายถึงตัวแปรที่t จะต้องเป็นความจริงจากหน่วยความจำสองครั้งถ้าt != typeof(int)ในขณะที่หลังจะ (สมมุติ) มักจะอ่านค่าของครั้งว่าt ความแตกต่างนี้สามารถทำลายความถูกต้องของรหัสที่เกิดขึ้นพร้อมกันซึ่งต้องอาศัยการรับรองที่ดีเยี่ยม สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้โปรดดูการเขียนโปรแกรมพร้อมกัน
Glenn Slayden

0

ตามเอกสารคำสั่งสวิตช์หากมีวิธีที่ไม่ชัดเจนในการแปลงวัตถุเป็นชนิดที่มีความหมายโดยปริยายแล้วมันจะได้รับอนุญาต ฉันคิดว่าคุณคาดหวังว่าพฤติกรรมที่แต่ละคำสั่งจะถูกแทนที่ด้วยif (t == typeof(int))แต่จะเปิดเวิร์มทั้งหมดของเวิร์มเมื่อคุณโอเวอร์โหลดโอเปอเรเตอร์นั้น พฤติกรรมจะเปลี่ยนไปเมื่อรายละเอียดการใช้งานสำหรับคำสั่ง switch เปลี่ยนไปหากคุณเขียนการแทนที่ == อย่างไม่ถูกต้อง โดยการลดการเปรียบเทียบประเภทและสตริงที่สำคัญและสิ่งเหล่านั้นที่สามารถลดลงเป็นประเภทที่สมบูรณ์ (และมีจุดมุ่งหมายเพื่อ) พวกเขาหลีกเลี่ยงปัญหาที่อาจเกิดขึ้น


0

เขียน:

"คำสั่ง switch ทำให้สาขาเวลาคงที่ไม่ว่าคุณจะมีกี่กรณี"

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

@mweerden - อ่าเข้าใจแล้ว ขอบคุณ

ฉันไม่มีประสบการณ์จำนวนมากใน C # และ. NET แต่ดูเหมือนว่านักออกแบบภาษาไม่อนุญาตให้มีการเข้าถึงแบบคงที่กับระบบประเภทยกเว้นในสถานการณ์ที่แคบ typeofผลตอบแทนคำหลักวัตถุนี้จึงสามารถเข้าถึงได้ตลอดเวลาทำงานเท่านั้น


0

ฉันคิดว่า Henk จับมันด้วยสิ่งที่ "ไม่มีการเข้าถึงระบบประเภท" อย่างเด็ดขาด

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


0

ฉันเห็นด้วยกับความคิดเห็นนี้ที่ใช้วิธีการขับเคลื่อนตารางมักจะดีกว่า

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


0

ฉันแทบจะไม่มีความรู้เกี่ยวกับ C # แต่ฉันสงสัยว่าสวิตช์ตัวใดตัวหนึ่งจะเกิดขึ้นในภาษาอื่นโดยไม่ต้องคิดให้กว้างขึ้นหรือนักพัฒนาตัดสินใจว่าการขยายมันไม่คุ้มค่า

พูดอย่างเคร่งครัดว่าคุณพูดถูกที่ไม่มีเหตุผลที่จะนำข้อ จำกัด เหล่านี้มาใช้ อาจสงสัยว่าเหตุผลคือสำหรับกรณีที่ได้รับอนุญาตการดำเนินการมีประสิทธิภาพมาก (ตามที่แนะนำโดย Brian Ensink ( 44921 )) แต่ฉันสงสัยว่าการใช้งานนั้นมีประสิทธิภาพมาก (wrt if-statement) ถ้าฉันใช้จำนวนเต็มและบางกรณีสุ่ม (เช่น 345, -4574 และ 1234203) และในกรณีใด ๆ สิ่งที่เป็นอันตรายในการอนุญาตให้ทุกอย่าง (หรืออย่างน้อย) และบอกว่ามันจะมีประสิทธิภาพสำหรับกรณีเฉพาะ (เช่น (เกือบ) หมายเลขติดต่อกัน)

อย่างไรก็ตามฉันสามารถจินตนาการได้ว่าคนหนึ่งอาจต้องการยกเว้นประเภทเนื่องจากเหตุผลเช่นประเภทที่กำหนดโดย lomaxx ( 44918 )

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


0

C # 8 ช่วยให้คุณสามารถแก้ปัญหานี้อย่างหรูหราและกะทัดรัดโดยใช้สวิทช์นิพจน์:

public string GetTypeName(object obj)
{
    return obj switch
    {
        int i => "Int32",
        string s => "String",
        { } => "Unknown",
        _ => throw new ArgumentNullException(nameof(obj))
    };
}

ดังนั้นคุณจะได้รับ:

Console.WriteLine(GetTypeName(obj: 1));           // Int32
Console.WriteLine(GetTypeName(obj: "string"));    // String
Console.WriteLine(GetTypeName(obj: 1.2));         // Unknown
Console.WriteLine(GetTypeName(obj: null));        // System.ArgumentNullException

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับคุณลักษณะใหม่ที่นี่

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