Buggy C# Code: 10 ข้อผิดพลาดที่พบบ่อยที่สุดในการเขียนโปรแกรม C#

เผยแพร่แล้ว: 2022-03-11

เกี่ยวกับ ซี ชาร์ป

C# เป็นหนึ่งในหลายภาษาที่กำหนดเป้าหมายไปที่ Microsoft Common Language Runtime (CLR) ภาษาที่กำหนดเป้าหมายไปที่ CLR จะได้รับประโยชน์จากคุณลักษณะต่างๆ เช่น การผสานรวมข้ามภาษาและการจัดการข้อยกเว้น การรักษาความปลอดภัยที่ได้รับการปรับปรุง รูปแบบที่ง่ายขึ้นสำหรับการโต้ตอบกับส่วนประกอบ และบริการดีบักและการทำโปรไฟล์ สำหรับภาษา CLR ในปัจจุบัน C# เป็นภาษาที่ใช้กันอย่างแพร่หลายมากที่สุดสำหรับโครงการพัฒนาระดับมืออาชีพที่ซับซ้อนซึ่งกำหนดเป้าหมายไปยังเดสก์ท็อป Windows อุปกรณ์พกพา หรือสภาพแวดล้อมเซิร์ฟเวอร์

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

เกี่ยวกับบทช่วยสอนการเขียนโปรแกรม C Sharp นี้

บทช่วยสอนนี้อธิบาย 10 ข้อผิดพลาดในการเขียนโปรแกรม C# ที่พบบ่อยที่สุดหรือปัญหาที่ควรหลีกเลี่ยงโดยโปรแกรมเมอร์ C# และให้ความช่วยเหลือ

แม้ว่าข้อผิดพลาดส่วนใหญ่ที่กล่าวถึงในบทความนี้เป็นแบบเฉพาะ C# แต่บางส่วนก็เกี่ยวข้องกับภาษาอื่นๆ ที่กำหนดเป้าหมายไปที่ CLR หรือใช้ประโยชน์จาก Framework Class Library (FCL)

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #1: การใช้ข้อมูลอ้างอิง เช่น ค่าหรือในทางกลับกัน

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

หากคุณไม่ทราบว่าวัตถุที่คุณกำลังใช้เป็นประเภทค่าหรือประเภทอ้างอิง คุณอาจพบกับความประหลาดใจบางอย่าง ตัวอย่างเช่น:

 Point point1 = new Point(20, 30); Point point2 = point1; point2.X = 50; Console.WriteLine(point1.X); // 20 (does this surprise you?) Console.WriteLine(point2.X); // 50 Pen pen1 = new Pen(Color.Black); Pen pen2 = pen1; pen2.Color = Color.Blue; Console.WriteLine(pen1.Color); // Blue (or does this surprise you?) Console.WriteLine(pen2.Color); // Blue

อย่างที่คุณเห็น ทั้งวัตถุ Point และ Pen ถูกสร้างขึ้นในลักษณะเดียวกัน แต่ค่าของ point1 ยังคงไม่เปลี่ยนแปลงเมื่อกำหนดค่าพิกัด X ใหม่ให้กับ point2 ในขณะที่ค่าของ pen1 ได้รับ การแก้ไขเมื่อมีการกำหนดสีใหม่ให้กับ pen2 2 . ดังนั้นเราจึง สรุปได้ ว่า point1 และ point2 แต่ละรายการมีสำเนาของวัตถุ Point ในขณะที่ pen1 และ pen2 มีการอ้างอิงไปยังวัตถุ Pen เดียวกัน แต่เราจะรู้ได้อย่างไรว่าถ้าไม่ได้ทำการทดลองนี้

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

 public struct Point { ... } // defines a “value” type public class Pen { ... } // defines a “reference” type

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

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

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #2: ความเข้าใจผิดเกี่ยวกับค่าเริ่มต้นสำหรับตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น

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

 class Program { static Point point1; static Pen pen1; static void Main(string[] args) { Console.WriteLine(pen1 == null); // True Console.WriteLine(point1 == null); // False (huh?) } }

เหตุใดจึงไม่ point1 null คำตอบคือ Point เป็นประเภทค่า และค่าเริ่มต้นสำหรับ Point คือ (0,0) ไม่ใช่ค่าว่าง การไม่จดจำสิ่งนี้เป็นข้อผิดพลาดที่ง่ายมาก (และเกิดขึ้นบ่อย) ใน C#

ค่าหลายประเภท (แต่ไม่ทั้งหมด) มีคุณสมบัติ IsEmpty ซึ่งคุณสามารถตรวจสอบได้ว่ามีค่าเท่ากับค่าเริ่มต้นหรือไม่:

 Console.WriteLine(point1.IsEmpty); // True

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

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #3: การใช้วิธีการเปรียบเทียบสตริงที่ไม่เหมาะสมหรือไม่ระบุ

มีหลายวิธีในการเปรียบเทียบสตริงใน C #

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

วิธีที่ต้องการในการทดสอบความเท่าเทียมกันของสตริงในการเขียนโปรแกรม C# คือใช้วิธี Equals :

 public bool Equals(string value); public bool Equals(string value, StringComparison comparisonType);

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

การใช้ลายเซ็นเมธอดของ Equals ที่มี comparisonType ทุกครั้งที่คุณเปรียบเทียบสตริง ไม่เพียงแต่จะทำให้โค้ดของคุณชัดเจนขึ้นเท่านั้น แต่ยังทำให้คุณคิดอย่างชัดเจนว่าคุณต้องเปรียบเทียบประเภทใด นี่เป็นสิ่งที่คุ้มค่าที่ต้องทำ เพราะแม้ว่าภาษาอังกฤษอาจไม่ได้ให้ความแตกต่างมากนักระหว่างการเปรียบเทียบเชิงลำดับและเชิงวัฒนธรรม แต่ภาษาอื่นๆ ก็มีมากมาย และการเพิกเฉยต่อความเป็นไปได้ของภาษาอื่น ๆ ก็เป็นการเปิดกว้างให้กับตัวคุณ ข้อผิดพลาดลงที่ถนน ตัวอย่างเช่น:

 string s = "strasse"; // outputs False: Console.WriteLine(s == "straße"); Console.WriteLine(s.Equals("straße")); Console.WriteLine(s.Equals("straße", StringComparison.Ordinal)); Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCulture)); Console.WriteLine(s.Equals("straße", StringComparison.OrdinalIgnoreCase)); // outputs True: Console.WriteLine(s.Equals("straße", StringComparison.CurrentCulture)); Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCultureIgnoreCase));

แนวทางปฏิบัติที่ปลอดภัยที่สุดคือจัดเตรียมพารามิเตอร์ comparisonType ให้กับเมธอด Equals เสมอ นี่คือแนวทางพื้นฐานบางประการ:

  • เมื่อเปรียบเทียบสตริงที่ผู้ใช้ป้อนหรือจะแสดงต่อผู้ใช้ ให้ใช้การเปรียบเทียบที่คำนึงถึงวัฒนธรรม ( CurrentCulture หรือ CurrentCultureIgnoreCase )
  • เมื่อเปรียบเทียบสตริงแบบเป็นโปรแกรม ให้ใช้การเปรียบเทียบลำดับ ( Ordinal หรือ OrdinalIgnoreCase )
  • InvariantCulture และ InvariantCultureIgnoreCase โดยทั่วไปจะไม่ถูกนำมาใช้ ยกเว้นในสถานการณ์ที่จำกัดมาก เนื่องจากการเปรียบเทียบลำดับจะมีประสิทธิภาพมากกว่า หากจำเป็นต้องเปรียบเทียบโดยคำนึงถึงวัฒนธรรม ควรทำการเปรียบเทียบกับวัฒนธรรมปัจจุบันหรือวัฒนธรรมเฉพาะอื่นๆ

นอกเหนือจากเมธอด Equals แล้ว สตริงยังมีเมธอด Compare ซึ่งให้ข้อมูลเกี่ยวกับลำดับสัมพัทธ์ของสตริง แทนที่จะเป็นเพียงการทดสอบความเท่าเทียมกัน วิธีนี้ดีกว่าตัวดำเนินการ < , <= , > และ >= ด้วยเหตุผลเดียวกับที่กล่าวข้างต้น เพื่อหลีกเลี่ยงปัญหา C#

ที่เกี่ยวข้อง: คำถามสัมภาษณ์ .NET ที่จำเป็น 12 ข้อ

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #4: การใช้คำสั่งซ้ำ (แทนการประกาศ) เพื่อจัดการคอลเลกชัน

ใน C# 3.0 การเพิ่ม Language-Integrated Query (LINQ) ให้กับภาษาเปลี่ยนแปลงไปตลอดกาลในลักษณะการสอบถามและจัดการคอลเลกชัน ตั้งแต่นั้นมา หากคุณใช้คำสั่งแบบวนซ้ำเพื่อจัดการคอลเล็กชัน คุณจะไม่ได้ใช้ LINQ ในเวลาที่ควรมี

โปรแกรมเมอร์ C# บางคนไม่รู้ด้วยซ้ำถึงการมีอยู่ของ LINQ แต่โชคดีที่จำนวนนั้นมีขนาดเล็กลงเรื่อยๆ หลายคนยังคงคิดว่าเนื่องจากความคล้ายคลึงกันระหว่างคำหลัก LINQ และคำสั่ง SQL การใช้งานเฉพาะในโค้ดที่สืบค้นฐานข้อมูล

แม้ว่าการสืบค้นฐานข้อมูลเป็นการใช้คำสั่ง LINQ ที่แพร่หลายมาก แต่จริง ๆ แล้วพวกมันทำงานบนคอลเลกชันที่นับได้ (เช่น วัตถุใดๆ ที่ใช้อินเทอร์เฟซ IEnumerable) ตัวอย่างเช่น หากคุณมีอาร์เรย์ของบัญชี แทนที่จะเขียนรายการ C# foreach:

 decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == "active") { total += account.Balance; } }

คุณสามารถเขียน:

 decimal total = (from account in myAccounts where account.Status == "active" select account.Balance).Sum();

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

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #5: ความล้มเหลวในการพิจารณาวัตถุพื้นฐานในคำสั่ง LINQ

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

ตัวอย่างเช่น พิจารณาข้อความต่อไปนี้:

 decimal total = (from account in myAccounts where account.Status == "active" select account.Balance).Sum();

จะเกิดอะไรขึ้นหากหนึ่งใน account.Status ของวัตถุ สถานะเท่ากับ "ใช้งานอยู่" (หมายเหตุตัวพิมพ์ใหญ่ A)? ถ้า myAccounts เป็นอ็อบเจ็กต์ DbSet (ที่ถูกตั้งค่าด้วยคอนฟิกูเรชันที่ไม่คำนึงถึงตัวพิมพ์เริ่มต้น) นิพจน์ where จะยังคงตรงกับองค์ประกอบนั้น อย่างไรก็ตาม หาก myAccounts อยู่ในอาร์เรย์ในหน่วยความจำ อาร์เรย์จะไม่ตรงกัน ดังนั้นจึงให้ผลลัพธ์รวมที่แตกต่างกัน

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

คำตอบคือเมื่ออ็อบเจ็กต์พื้นฐานในคำสั่ง LINQ อ้างอิงถึงข้อมูลตาราง SQL (เช่นเดียวกับอ็อบเจ็กต์ Entity Framework DbSet ในตัวอย่างนี้) คำสั่งนั้นจะถูกแปลงเป็นคำสั่ง T-SQL จากนั้นโอเปอเรเตอร์จะปฏิบัติตามกฎการเขียนโปรแกรม T-SQL ไม่ใช่กฎการเขียนโปรแกรม C# ดังนั้นการเปรียบเทียบในกรณีข้างต้นจึงกลายเป็นไม่คำนึงถึงขนาดตัวพิมพ์

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

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #6: สับสนหรือปลอมแปลงโดยวิธีการขยาย

ดังที่กล่าวไว้ก่อนหน้านี้ คำสั่ง LINQ ทำงานบนอ็อบเจกต์ใดๆ ที่ใช้ IEnumerable ตัวอย่างเช่น ฟังก์ชันง่าย ๆ ต่อไปนี้จะเพิ่มยอดคงเหลือในบัญชีใด ๆ ก็ตาม:

 public decimal SumAccounts(IEnumerable<Account> myAccounts) { return myAccounts.Sum(a => a.Balance); }

ในโค้ดด้านบนนี้ ประเภทของพารามิเตอร์ myAccounts ถูกประกาศเป็น IEnumerable<Account> เนื่องจาก myAccounts อ้างอิงถึงวิธี Sum (C# ใช้ “เครื่องหมายจุด” ที่คุ้นเคยเพื่ออ้างอิงวิธีการในคลาสหรืออินเทอร์เฟซ) เราจึงคาดว่าจะเห็นวิธีการที่เรียกว่า Sum() ในคำจำกัดความของอินเทอร์เฟซ IEnumerable<T> อย่างไรก็ตาม คำจำกัดความของ IEnumerable<T> ไม่ได้อ้างอิงถึงวิธี Sum ใด ๆ และมีลักษณะดังนี้:

 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }

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

คำตอบคือ Sum() ไม่ใช่วิธีการที่กำหนดไว้ในอินเทอร์เฟซ IEnumerable แต่เป็นวิธีการแบบคงที่ (เรียกว่า “วิธีการขยาย”) ที่กำหนดไว้ในคลาส System.Linq.Enumerable :

 namespace System.Linq { public static class Enumerable { ... // the reference here to “this IEnumerable<TSource> source” is // the magic sauce that provides access to the extension method Sum public static decimal Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector); ... } }

แล้วอะไรที่ทำให้วิธีการขยายแตกต่างจากวิธีสแตติกอื่น ๆ และอะไรทำให้เราสามารถเข้าถึงได้ในคลาสอื่น ๆ ?

ลักษณะเด่นของวิธีการขยายคือตัวดัดแปลง this ในพารามิเตอร์ตัวแรก นี่คือ "เวทมนตร์" ที่ระบุให้คอมไพเลอร์เป็นวิธีการขยาย ประเภทของพารามิเตอร์ที่แก้ไข (ในกรณีนี้คือ IEnumerable<TSource> ) หมายถึงคลาสหรืออินเทอร์เฟซซึ่งจะปรากฏขึ้นเพื่อใช้เมธอดนี้

(สำหรับจุดด้านข้าง ไม่มีอะไรมหัศจรรย์เกี่ยวกับความคล้ายคลึงกันระหว่างชื่อของอินเทอร์เฟซ IEnumerable และชื่อของคลาส Enumerable ซึ่งกำหนดวิธีการขยาย ความคล้ายคลึงกันนี้เป็นเพียงทางเลือกโวหารโดยพลการ)

ด้วยความเข้าใจนี้ เราจะเห็นได้ว่าฟังก์ชัน sumAccounts ที่เราแนะนำข้างต้นสามารถนำไปใช้แทนได้ดังนี้:

 public decimal SumAccounts(IEnumerable<Account> myAccounts) { return Enumerable.Sum(myAccounts, a => a.Balance); }

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

วิธีการขยายถูกรวมเข้ากับการ using [namespace]; คำสั่งที่ด้านบนของไฟล์ คุณจำเป็นต้องรู้ว่าเนมสเปซ C# ใดมีวิธีส่วนขยายที่คุณต้องการ แต่นั่นก็ค่อนข้างง่ายที่จะระบุเมื่อคุณรู้ว่าคุณกำลังค้นหาอะไรอยู่

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

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

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

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

การใช้วิธีการขยายในไลบรารี C# เป็นที่แพร่หลายมากขึ้น นอกจาก LINQ แล้ว Unity Application Block และเฟรมเวิร์กของ Web API ยังเป็นตัวอย่างของไลบรารีสมัยใหม่สองไลบรารีที่มีการใช้งานอย่างหนักโดย Microsoft ซึ่งใช้วิธีการขยายด้วยเช่นกัน และยังมีวิธีอื่นๆ อีกมากมาย ยิ่งเฟรมเวิร์กมีความทันสมัยมากขึ้นเท่าไร ก็ยิ่งมีโอกาสมากขึ้นที่จะรวมวิธีการขยายเข้าไว้ด้วยกัน

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

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

C# มีอ็อบเจ็กต์คอลเลกชันที่หลากหลาย โดยต่อไปนี้เป็นเพียงรายการบางส่วนเท่านั้น:
Array , ArrayList , BitArray , BitVector32 , Dictionary<K,V> , HashTable , HybridDictionary , List<T> , NameValueCollection , OrderedDictionary , Queue, Queue<T> , SortedList , Stack, Stack<T> , StringCollection , StringDictionary

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

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

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

ปัญหา C# ทั่วไปอีกประการหนึ่งคือการเขียนวัตถุคอลเลกชันของคุณเอง ไม่ได้หมายความว่ามันไม่เหมาะสม แต่ด้วยตัวเลือกที่ครอบคลุมเหมือนที่ .NET นำเสนอ คุณอาจประหยัดเวลาได้มากโดยใช้หรือขยายตัวเลือกที่มีอยู่แล้ว แทนที่จะสร้างวงล้อขึ้นใหม่ โดยเฉพาะอย่างยิ่ง C5 Generic Collection Library สำหรับ C# และ CLI มีคอลเลกชั่นเพิ่มเติมมากมาย "แบบสำเร็จรูป" เช่น โครงสร้างข้อมูลทรีแบบถาวร ลำดับความสำคัญตามฮีป รายการอาร์เรย์ที่จัดทำดัชนีแฮช รายการที่เชื่อมโยง และอื่นๆ อีกมากมาย

ข้อผิดพลาดในการเขียนโปรแกรม C# ทั่วไป #8: ละเลยทรัพยากรฟรี

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

แม้ว่าเมธอด destructor สามารถกำหนดได้ในคลาส C# ใดๆ ก็ตาม ปัญหาของ destructors (เรียกอีกอย่างว่า finalizers ใน C#) คือคุณไม่รู้แน่ชัดว่าจะถูกเรียกเมื่อใด พวกเขาถูกเรียกโดยตัวรวบรวมขยะ (บนเธรดที่แยกต่างหากซึ่งอาจทำให้เกิดความยุ่งยากเพิ่มเติม) ในเวลาที่ไม่แน่นอนในอนาคต การพยายามหลีกเลี่ยงข้อจำกัดเหล่านี้โดยบังคับให้มีการรวบรวมขยะด้วย GC.Collect() ไม่ใช่แนวทางปฏิบัติที่ดีที่สุดสำหรับ C# เนื่องจากจะบล็อกเธรดในระยะเวลาที่ไม่ทราบขณะที่รวบรวมวัตถุทั้งหมดที่มีสิทธิ์ในการรวบรวม

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

การรั่วไหลของทรัพยากรเป็นปัญหาในเกือบทุกสภาพแวดล้อม อย่างไรก็ตาม C# มีกลไกที่แข็งแกร่งและใช้งานง่าย ซึ่งหากใช้งานแล้ว การรั่วไหลจะเกิดขึ้นได้ยากขึ้นมาก .NET framework กำหนดอินเทอร์เฟซ IDisposable ซึ่งประกอบด้วยเมธอด Dispose() เท่านั้น อ็อบเจ็กต์ใดๆ ที่ใช้ IDisposable คาดว่าจะมีการเรียกเมธอดนั้นเมื่อใดก็ตามที่ผู้บริโภคของอ็อบเจ็กต์จัดการมันเสร็จแล้ว ส่งผลให้มีการปล่อยทรัพยากรอย่างชัดเจนและกำหนดขึ้นได้

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

 using (FileStream myFile = File.OpenRead("foo.txt")) { myFile.Read(buffer, 0, 100); }

ด้วยการสร้างบล็อกการ using งานในตัวอย่างข้างต้น คุณจะทราบแน่นอนว่า myFile.Dispose() จะถูกเรียกทันทีที่คุณทำไฟล์เสร็จ ไม่ว่า Read() จะส่งข้อยกเว้นหรือไม่ก็ตาม

ข้อผิดพลาดในการเขียนโปรแกรม C # ทั่วไป #9: ละเลยข้อยกเว้น

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

ตัวอย่างเช่น ต่อไปนี้คือสองวิธีที่แตกต่างกันในการแสดงประเภทที่ชัดเจนใน C#:

 // METHOD 1: // Throws an exception if account can't be cast to SavingsAccount SavingsAccount savingsAccount = (SavingsAccount)account; // METHOD 2: // Does NOT throw an exception if account can't be cast to // SavingsAccount; will just set savingsAccount to null instead SavingsAccount savingsAccount = account as SavingsAccount;

ข้อผิดพลาดที่ชัดเจนที่สุดที่อาจเกิดขึ้นกับการใช้วิธีที่ 2 คือความล้มเหลวในการตรวจสอบค่าที่ส่งกลับ ซึ่งอาจส่งผลให้เกิด NullReferenceException ในท้ายที่สุด ซึ่งอาจปรากฏขึ้นในเวลาต่อมา ทำให้ยากต่อการติดตามแหล่งที่มาของปัญหา ในทางตรงกันข้าม วิธีที่ 1 จะโยน InvalidCastException ออกทันที ทำให้แหล่งที่มาของปัญหาชัดเจนขึ้นในทันที

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

ต่อไปนี้คือตัวอย่างสองสามตัวอย่างของเมธอดทั่วไปอื่นๆ โดยที่วิธีหนึ่งมีข้อยกเว้นและอีกวิธีหนึ่งไม่มี:

 int.Parse(); // throws exception if argument can't be parsed int.TryParse(); // returns a bool to denote whether parse succeeded IEnumerable.First(); // throws exception if sequence is empty IEnumerable.FirstOrDefault(); // returns null/default value if sequence is empty

นักพัฒนา C# บางคนนั้น “มีข้อยกเว้นที่ไม่พึงประสงค์” มากจนพวกเขาถือว่าวิธีการที่ไม่มีข้อยกเว้นนั้นเหนือกว่าโดยอัตโนมัติ แม้ว่าจะมีบางกรณีที่อาจเป็นจริง แต่ก็ไม่ถูกต้องตามหลักเกณฑ์ทั่วไป

ตัวอย่างเช่น ในกรณีที่คุณมีทางเลือกอื่นที่ถูกต้องตามกฎหมาย (เช่น ค่าเริ่มต้น) ที่จะทำหากมีการสร้างข้อยกเว้น วิธีที่ไม่ใช่ข้อยกเว้นอาจเป็นทางเลือกที่ถูกต้อง ในกรณีเช่นนี้ มันอาจจะดีกว่าที่จะเขียนสิ่งนี้:

 if (int.TryParse(myString, out myInt)) { // use myInt } else { // use default value }

แทน:

 try { myInt = int.Parse(myString); // use myInt } catch (FormatException) { // use default value }

อย่างไรก็ตาม ไม่ถูกต้องที่จะถือว่า TryParse เป็นวิธีที่ "ดีกว่า" บางครั้งก็เป็นอย่างนั้น บางครั้งก็ไม่ใช่ นั่นเป็นเหตุผลที่มีสองวิธีในการทำ ใช้สิ่งที่ถูกต้องสำหรับบริบทที่คุณอยู่ จำไว้ว่าข้อยกเว้นสามารถเป็นเพื่อนของคุณในฐานะนักพัฒนาซอฟต์แวร์ได้อย่างแน่นอน

ข้อผิดพลาดในการเขียนโปรแกรม C # ทั่วไป #10: อนุญาตให้คอมไพเลอร์เตือนการสะสม

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

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

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

แต่ถ้าคุณเพิกเฉยต่อคำเตือนประเภทนี้ ไม่ช้าก็เร็ว บางสิ่งเช่นนี้อาจพบทางเข้าโค้ดของคุณ:

 class Account { int myId; int Id; // compiler warned you about this, but you didn't listen! // Constructor Account(int id) { this.myId = Id; // OOPS! } }

และด้วยความเร็วที่ Intellisense ทำให้เราสามารถเขียนโค้ดได้ ข้อผิดพลาดนี้ไม่น่าจะเป็นไปได้อย่างที่เห็น

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

จำไว้ว่าคอมไพเลอร์ C Sharp ให้ข้อมูลที่เป็นประโยชน์มากมายเกี่ยวกับความทนทานของโค้ดของคุณ... หากคุณกำลังฟังอยู่ อย่าละเลยคำเตือน โดยปกติจะใช้เวลาเพียงไม่กี่วินาทีในการแก้ไข และการแก้ไขเมื่อเกิดขึ้นใหม่สามารถช่วยคุณประหยัดเวลาได้หลายชั่วโมง ฝึกฝนตัวเองให้คาดหวังว่าหน้าต่าง "รายการข้อผิดพลาด" ของ Visual Studio จะแสดง "ข้อผิดพลาด 0 รายการ คำเตือน 0 รายการ" เพื่อให้คำเตือนใดๆ ทำให้คุณรู้สึกไม่สบายใจที่จะแก้ไขในทันที

แน่นอนว่ามีข้อยกเว้นสำหรับกฎทุกข้อ ดังนั้น อาจมีบางครั้งที่โค้ดของคุณจะดูไม่น่าสนใจสำหรับคอมไพเลอร์ แม้ว่าจะเป็นไปตามที่คุณตั้งใจไว้ก็ตาม ในกรณีที่หายากมากเหล่านั้น ให้ใช้ #pragma warning disable [warning id] เฉพาะโค้ดที่ทริกเกอร์คำเตือน และสำหรับ ID คำเตือนที่ทริกเกอร์เท่านั้น การดำเนินการนี้จะระงับคำเตือนนั้นและคำเตือนนั้นเท่านั้น เพื่อให้คุณยังคงตื่นตัวสำหรับคำเตือนใหม่ๆ

สรุป

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

การใช้บทช่วยสอน C Sharp แบบนี้เพื่อทำความคุ้นเคยกับความแตกต่างที่สำคัญของ C# เช่น (แต่ไม่จำกัดเพียง) ปัญหาที่เกิดขึ้นในบทความนี้ จะช่วยในการเพิ่มประสิทธิภาพ C# ในขณะที่หลีกเลี่ยงข้อผิดพลาดทั่วไปบางประการของ ภาษา.


อ่านเพิ่มเติมในบล็อก Toptal Engineering:

  • คำถามสัมภาษณ์ C# ที่จำเป็น
  • C # กับ C ++: Core คืออะไร?