สลับกับพฤติกรรมแปลก ๆ var / null


91

ระบุรหัสต่อไปนี้:

string someString = null;
switch (someString)
{
    case string s:
        Console.WriteLine("string s");
        break;
    case var o:
        Console.WriteLine("var o");
        break;
    default:
        Console.WriteLine("default");
        break;
}

ทำไมงบเปลี่ยนการจับคู่ในcase var o?

เป็นความเข้าใจของฉันที่case string sไม่ตรงกันเมื่อใดs == nullเนื่องจาก (ได้ผล) (null as string) != nullประเมินเป็นเท็จ IntelliSense บน VS Code บอกฉันว่าoเป็นstringเช่นกัน ความคิดใด ๆ ?


คล้ายกับ: C # 7 switch case พร้อมการตรวจสอบ null


9
ได้รับการยืนยัน ฉันรักคำถามนี้โดยเฉพาะอย่างยิ่งกับการสังเกตว่าoเป็นstring(ได้รับการยืนยันกับ generics - คือFoo(o)ที่Foo<T>(T template) => typeof(T).Name) - มันเป็นกรณีที่น่าสนใจมากที่string xจะทำงานที่แตกต่างกว่าvar xแม้ในขณะที่xพิมพ์ (โดยคอมไพเลอร์) เป็นstring
Marc Gravell

7
กรณีเริ่มต้นคือรหัสตาย เชื่อว่าเราควรออกคำเตือนที่นั่น กำลังตรวจสอบ
JaredPar

13
เป็นเรื่องแปลกสำหรับฉันที่นักออกแบบ C # ตัดสินใจที่จะอนุญาตvarในบริบทนี้เลย สิ่งนี้ดูเหมือนจะเป็นสิ่งที่ฉันพบใน C ++ ไม่ใช่ในภาษาที่อ้างว่าจะนำโปรแกรมเมอร์ "ไปสู่หลุมแห่งความสำเร็จ" ที่นี่varมีทั้งคลุมเครือและไร้ประโยชน์สิ่งที่การออกแบบ C # มักจะพยายามหลีกเลี่ยง
Peter Duniho

1
@PeterDuniho ฉันจะไม่พูดว่าไร้ประโยชน์; นิพจน์ขาเข้าของswitchอาจไม่สามารถออกเสียงได้ - ประเภทที่ไม่ระบุตัวตน ฯลฯ ; และไม่คลุมเครือ - คอมไพเลอร์รู้ประเภทอย่างชัดเจน มันเป็นเพียงความสับสน (สำหรับฉันอย่างน้อย) ว่าnullกฎแตกต่างกันมาก!
Marc Gravell

1
@PeterDuniho fun fact - ครั้งหนึ่งเราได้ค้นหากฎที่เป็นทางการของการมอบหมายงานที่ชัดเจนจากข้อกำหนด C # 1.2 และโค้ดส่วนขยายที่แสดงภาพประกอบมีการประกาศตัวแปรภายในบล็อก (ซึ่งตอนนี้อยู่ในขณะนี้) มันย้ายออกไปข้างนอกใน 2.0 เท่านั้นจากนั้นกลับเข้าไปข้างในอีกครั้งเมื่อปัญหาการจับภาพนั้นชัดเจน
Marc Gravell

คำตอบ:


69

ภายในswitchคำสั่งการจับคู่รูปแบบโดยใช้ a caseสำหรับชนิดที่ชัดเจนจะถามว่าค่าที่เป็นปัญหาเป็นประเภทเฉพาะนั้นหรือเป็นประเภทที่ได้รับ มันเทียบเท่ากันแน่นอนของis

switch (someString) {
  case string s:
}
if (someString is string) 

ค่าnullนี้ไม่มีประเภทดังนั้นจึงไม่เป็นไปตามเงื่อนไขข้างต้น ประเภทคงที่ของsomeStringไม่เข้ามามีบทบาทในตัวอย่างใดตัวอย่างหนึ่ง

varชนิดแม้ว่าจะอยู่ในรูปแบบการจับคู่ทำหน้าที่เป็นป่าการ์ดและจะตรงกับค่าใด ๆ nullรวมทั้ง

defaultกรณีที่นี่เป็นรหัสที่ตายแล้ว ค่าcase var oนี้จะจับคู่ค่าว่างหรือไม่เป็นค่าว่าง กรณีที่ไม่ใช่ค่าเริ่มต้นจะชนะเหนือค่าเริ่มต้นเสมอดังนั้นdefaultจะไม่ถูกตี หากคุณดู IL คุณจะเห็นว่ามันไม่ได้เปล่งออกมาเลย

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

bool b = ...;
switch (b) {
  case true: ...
  case false: ...
  default: ...
}

ที่นี่defaultจะไม่ถูกตี (แม้ว่าจะboolมีค่าที่ไม่ใช่ 1 หรือ 0) แต่ C # ได้อนุญาตสิ่งนี้ตั้งแต่ 1.0 โดยไม่มีการเตือน การจับคู่รูปแบบจะสอดคล้องกับลักษณะการทำงานนี้ที่นี่


4
แม้ว่าปัญหาที่แท้จริงก็คือคอมไพเลอร์ "แสดง" varเป็นประเภทstringเมื่อมันไม่ได้จริงๆ (ไม่แน่ใจว่าควรยอมรับประเภทไหน)
shmuelie

@shmuelie ประเภทของในตัวอย่างการคำนวณให้เป็นvar string
JaredPar

5
@JaredPar ขอบคุณสำหรับข้อมูลเชิงลึกที่นี่; โดยส่วนตัวฉันจะสนับสนุนการส่งเสียงเตือนมากขึ้นแม้ว่าจะไม่ได้ทำก่อนหน้านี้ แต่ฉันเข้าใจข้อ จำกัด ของทีมภาษา คุณเคยพิจารณาโหมด "ครวญครางเกี่ยวกับทุกสิ่ง" (อาจเป็นไปได้โดยค่าเริ่มต้น) เทียบกับ "โหมดอดทนแบบเดิม" (วิชาเลือก) หรือไม่ อาจจะcsc /stiffUpperLip
Marc Gravell

3
@MarcGravell เรามีคุณลักษณะที่เรียกว่าคลื่นเตือนซึ่งมีไว้เพื่อให้ง่ายขึ้นใช้งานร่วมกันได้น้อยลงเพื่อแนะนำคำเตือนใหม่ ๆ โดยพื้นฐานแล้วคอมไพเลอร์รีลีสทุกตัวเป็นคลื่นลูกใหม่และคุณสามารถเลือกใช้คำเตือนผ่าน / wave: 1, / wave: 2, / wave: all
JaredPar

4
@JonathanDickinson ฉันไม่คิดว่านั่นแสดงว่าคุณคิดว่ามันแสดงให้เห็น นั่นแสดงให้เห็นว่า a nullเป็นการstringอ้างอิงที่ถูกต้องและstringการอ้างอิงใด ๆ(รวมถึงnull) สามารถส่งโดยปริยาย (การสงวนข้อมูลอ้างอิง) ไปยังการobjectอ้างอิงและobjectการอ้างอิงใด ๆที่nullสามารถอัปเดต (อย่างชัดเจน) เป็นประเภทอื่นได้nullสำเร็จ ไม่เหมือนกันจริงๆในแง่ของระบบประเภทคอมไพเลอร์
Marc Gravell

22

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

case string s:

ถูกตีความว่าif(someString is string) { s = (string)someString; ...หรือif((s = (someString as string)) != null) { ... }- ซึ่งเกี่ยวข้องกับการnullทดสอบ - ซึ่งล้มเหลวในกรณีของคุณ ตรงกันข้าม:

case var o:

โดยที่คอมไพลเลอร์แก้ไขoตามที่เป็นstringอยู่o = (string)someString; ...- ไม่มีnullการทดสอบแม้ว่าจะมีลักษณะคล้ายกันบนพื้นผิวก็ตามเพียงแค่คอมไพเลอร์ระบุประเภท

สุดท้าย:

default:

ที่นี่ไม่สามารถเข้าถึงได้เนื่องจากกรณีด้านบนจับทุกอย่างได้ นี่อาจเป็นบั๊กของคอมไพเลอร์ที่ไม่ได้ส่งคำเตือนรหัสที่ไม่สามารถเข้าถึงได้

ฉันยอมรับว่านี่เป็นเรื่องละเอียดอ่อนและเหมาะสมมากและสับสน แต่เห็นได้ชัดว่าcase var oสถานการณ์ใช้กับการแพร่กระจายแบบว่าง ( o?.Length ?? 0ฯลฯ ) ฉันยอมรับว่ามันแปลกที่มันทำงานแตกต่างกันมากระหว่างvar oและstring sแต่มันเป็นสิ่งที่คอมไพเลอร์ทำอยู่ในปัจจุบัน


14

เป็นเพราะcase <Type>ตรงกับประเภทไดนามิก (รันไทม์) ไม่ใช่ประเภทคงที่ (เวลาคอมไพล์) ไม่ได้มีชนิดแบบไดนามิกดังนั้นจึงไม่สามารถจับคู่กับnull เป็นเพียงทางเลือกเท่านั้นstringvar

(โพสต์เพราะชอบตอบสั้น ๆ )

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