นี่เป็นคำถามที่สมาชิกในทีมของเราคนหนึ่งถามในระหว่างขั้นตอนการเขียนโปรแกรม เนื่องจากข้อผิดพลาดในการคอมไพล์นี้ง่ายต่อการหลีกเลี่ยง ฉันจึงไม่เคยคิดถึงปัญหานี้อย่างรอบคอบเลย จนกระทั่งฉันได้ดูโค้ดของเขาและพบว่าปัญหานี้ไม่ง่ายขนาดนั้น
ลองดูรหัสนี้ก่อน:
รหัส
โปรแกรมชั้นเรียน
-
โมฆะคงที่หลัก (สตริง [] args)
-
ไบต์ [] buf = ไบต์ใหม่ [1024];
T t = ใหม่ T();
สตริง str = "1234";
อินท์ n = 1234;
intnn = 1234;
DateTime dt = DateTime.Now;
วัตถุ o = 1234;
Console.WriteLine("เสร็จสิ้น");
-
-
คลาส ที { }
คุณคิดว่ามีตัวแปรกี่ตัวที่ไม่ได้ใช้ในโค้ดนี้
หากคุณมองจากมุมมองของโปรแกรมเมอร์ คำตอบควรเป็นว่าตัวแปรทั้งหมดไม่ได้ใช้ แต่ผลลัพธ์ที่ได้รับจากคอมไพเลอร์นั้นขัดกับสัญชาตญาณเล็กน้อย:
ตัวแปร "str" ได้รับการกำหนด แต่ค่าของมันไม่เคยถูกใช้ ตัวแปร "n" ได้รับการกำหนด แต่ค่าของมันไม่เคยถูกใช้ ไม่เคยใช้ค่า.
สิ่งที่แปลกคือแม้ว่าตัวแปรทั้งหมดจะถูกประกาศในลักษณะเดียวกัน แต่คอมไพเลอร์ก็คิดว่าเพียงบางส่วนเท่านั้นที่ไม่ได้ใช้ เกิดอะไรขึ้น?
มาวิเคราะห์กันทีละรายการ ดูที่อาร์เรย์ก่อน หากใช้ค่าเริ่มต้น ข้อมูลที่คอมไพเลอร์กำหนดจะแตกต่างออกไป:
ไบต์ [] buf1 = null; // มีคำเตือน
ไบต์ [] buf2 = ไบต์ใหม่ [1024]; // ไม่มีการเตือน
ผลลัพธ์นี้ดูเหมือนจะบ่งชี้ว่าหากการกำหนดพารามิเตอร์เป็นโมฆะ คอมไพลเลอร์จะไม่ดำเนินการกำหนดจริง และตัวแปรจะได้รับการปฏิบัติเสมือนว่าไม่ได้ใช้ ผลลัพธ์ของการตรวจสอบ IL ยังสามารถพิสูจน์คำสั่งนี้ได้: สำหรับบรรทัดแรก คอมไพเลอร์ไม่ได้สร้างคำสั่งใด ๆ ที่เกี่ยวข้อง สำหรับบรรทัดที่สอง คำสั่ง newattr ถูกใช้เพื่อสร้างอาร์เรย์
สำหรับคลาสแบบกำหนดเอง:
T t1 = null; // มีคำเตือน
T t2 = ใหม่ T(); // ไม่มีการเตือน
ผลลัพธ์นี้ควรจะเข้าใจได้ (แม้ว่าจะเข้าใจได้ แต่ฉันไม่คิดว่าจะดีด้วยเหตุผลที่แสดงด้านล่าง) แม้ว่าเราไม่ได้เรียกเมธอดใดๆ ของคลาส แต่ Constructor ของคลาสอาจยังคงดำเนินการบางอย่าง ดังนั้นตราบใดที่คลาสถูกสร้างขึ้น คอมไพเลอร์จะถือว่าคลาสนั้นถูกใช้ราวกับว่ามันถูกใช้ไปแล้ว
สำหรับประเภทค่าพื้นฐาน ลักษณะการทำงานจะแตกต่างจากประเภทการอ้างอิง คอมไพลเลอร์ไม่ถือว่าการกำหนดเริ่มต้นเป็นการใช้ตัวแปร:
int n1 = 0; // มีคำเตือน
int n2 = 1234; // มีคำเตือน
int? n3 = null; // มีคำเตือน
int? n4 = 0; // มีคำเตือน
int? n5 = 1234; // มีคำเตือน
สตริงควรถือเป็นประเภทการอ้างอิงในแง่ของการใช้งาน แต่ประสิทธิภาพของสตริงจะคล้ายกับประเภทค่ามากกว่า และข้อมูลคำเตือนจะเหมือนกับประเภทค่า
สำหรับประเภทค่าที่ซับซ้อนกว่าเล็กน้อย ผลลัพธ์ที่ได้จะละเอียดกว่าเล็กน้อย:
DateTime dt1; // มีคำเตือน
DateTime dt2 = new DateTime(); // มีคำเตือน
DateTime dt3 = DateTime ใหม่ (2009,1,1); // ไม่มีคำเตือน
DateTime dt4 = DateTime.Now; // ไม่มีการเตือน
มีสิ่งหนึ่งที่ควรทราบเกี่ยวกับผลลัพธ์นี้ แม้ว่าตัวสร้างเริ่มต้นของ DateTime และตัวสร้างพารามิเตอร์จะเป็นทั้งตัวสร้างจากมุมมองของผู้ใช้ แต่ก็แตกต่างจากมุมมองของคอมไพเลอร์ นอกจากนี้ยังสามารถเห็นได้จากการคอมไพล์ด้วย IL ว่าหากเรียกใช้คอนสตรัคเตอร์เริ่มต้น คอมไพลเลอร์จะเรียกใช้คำสั่ง initobj ในขณะที่คำสั่งการเรียก ctor ใช้สำหรับคอนสตรัคเตอร์ที่กำหนดพารามิเตอร์ นอกจากนี้ แม้ว่ารูปแบบของโค้ดการมอบหมายงานจะเหมือนกันทุกประการจากมุมมองของโปรแกรมเมอร์ แต่คอมไพลเลอร์จะใช้กลยุทธ์การสร้างที่แตกต่างกันขึ้นอยู่กับค่าที่กำหนด ซึ่งขัดกับสัญชาตญาณเช่นกัน
ข้อสรุปสุดท้ายเป็นเรื่องที่น่าเสียใจ กล่าวคือ คำเตือนการคอมไพล์ของ C# ไม่เพียงพอที่จะให้โปรแกรมเมอร์มีการป้องกันที่เพียงพอ โดยเฉพาะอย่างยิ่งสำหรับอาร์เรย์:
ไบต์ [] buf = ไบต์ใหม่ [1024];
หากคุณสร้างอาร์เรย์ดังกล่าวโดยไม่ใช้งาน คอมไพลเลอร์จะไม่ส่งข้อความเตือนใดๆ ให้กับโปรแกรมเมอร์
อีกประเด็นที่ควรพิจารณาคือการประกาศคลาสโดยไม่ใช้วิธีใดๆ เช่น just
T เสื้อ = ใหม่ T()
นี่เป็นพฤติกรรมที่สมเหตุสมผลหรือไม่? คอมไพเลอร์ควรออกคำเตือนสำหรับสิ่งนี้หรือไม่
ความเห็นส่วนตัวของฉันคือจากมุมมองของการใช้งาน สิ่งนี้ไม่สมเหตุสมผลและควรหลีกเลี่ยงให้มากที่สุด คอมไพเลอร์ควรออกคำเตือนหากพบการใช้งานนี้ หากจำเป็น คุณสามารถหลีกเลี่ยงข้อความเตือนได้โดยการประกาศโดยเฉพาะผ่านคำสั่งการคอมไพล์หรือเมธอด Attribute อย่างไรก็ตาม พฤติกรรมของคอมไพเลอร์ C# ไม่ใช่การออกคำเตือน ซึ่งฉันไม่เห็นด้วย แน่นอนฉันยังหวังว่าทุกคนจะเสนอความคิดของตนเอง