ทำไม List<string>
จึงไม่สามารถเก็บไว้ใน List<object>
ใน C#?
C# เป็นภาษาโปรแกรมที่ทรงพลังซึ่งมีการกำหนดประเภทที่เข้มงวดและคุณสมบัติของวัตถุ เป็นหนึ่งในปัญหาที่พบบ่อยที่นักพัฒนาพบคือปัญหาเกี่ยวกับ generics โดยเฉพาะเมื่อจัดการกับรายการ คำถามที่มักถูกถามคือ: ทำไมวัตถุ List<string>
จึงไม่สามารถเก็บไว้ในตัวแปร List<object>
ได้? มาดูรายละเอียดของหัวข้อนี้เพื่อเข้าใจหลักการและผลที่ตามมาของพฤติกรรมนี้
เข้าใจรายการกับ Generics
ใน C# List<T>
เป็นคอลเลกชัน generic ที่สามารถเก็บรายการหลายรายการของประเภทที่กำหนด T
ได้ ตัวอย่างเช่น List<string>
สามารถเก็บเฉพาะสตริงเท่านั้น ในขณะที่ List<object>
สามารถเก็บวัตถุที่เป็นประเภทใดก็ได้เพราะ object
เป็นคลาสพื้นฐานสำหรับทุกประเภทใน C# สิ่งนี้นำไปสู่แง่มุมที่สำคัญของความปลอดภัยในประเภทใน C#
ปัญหาหลัก
เมื่อคุณพยายามจะเก็บ List<string>
ไว้ใน List<object>
คุณจะพบข้อผิดพลาดดังต่อไปนี้:
Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<object>'
นี่เป็นเพราะ C# ไม่อนุญาตให้มีการแปลงหรือการแคสต์โดยตรงระหว่างประเภท generic ที่แตกต่างกัน ในศัพท์ที่เข้าใจง่ายคือ:
List<string>
และList<object>
เป็นประเภทที่แตกต่างกัน- C# บังคับใช้การแยกประเภทนี้เพื่อให้แน่ใจว่ารหัสของคุณปลอดภัย
ทำไมเรื่องนี้ถึงสำคัญ?
มาดูกันว่าทำไมความปลอดภัยในประเภทนี้จึงมีความสำคัญ ลองนึกภาพว่าคุณสามารถแคสต์ List<string>
เป็น List<object>
และจากนั้นเพิ่มวัตถุประเภทอื่น (เช่น Foo
) ลงในรายการนั้น รายการจะมีทั้งสตริงและวัตถุ Foo
ซึ่งจะทำให้เกิดความไม่สอดคล้องกัน ในภายหลัง หากคุณพยายามเข้าถึงองค์ประกอบจากรายการนี้ในฐานะสตริง คุณจะพบกับข้อผิดพลาดในขณะรันไทม์—โดยเฉพาะ ClassCastException
—เมื่อรหัสของคุณพบกับอินสแตนซ์ Foo
สถานการณ์ตัวอย่าง
พิจารณาตามโค้ดต่อไปนี้:
List<string> sl = new List<string>();
List<object> ol;
ol = sl; // บรรทัดนี้สร้างข้อผิดพลาด
หากโค้ดนี้คอมไพล์ได้ คุณสามารถเพิ่มองค์ประกอบเช่น:
ol.Add(new Foo()); // ตอนนี้เรามี Foo ในรายการสตริง
หลังจากนั้นการวนลูปผ่าน sl
และพยายามแคสต์กลับไปเป็นสตริงจะล้มเหลวเพราะไม่ใช่ทุกองค์ประกอบจะเป็นสตริงอีกต่อไป
การทดลองแคสต์ที่ชัดเจน
คุณอาจทดลองใช้การแคสต์ที่ชัดเจน พยายามแปลง List<string>
เป็น List<object>
ดังนี้:
sl = (List<object>)ol; // นี่ก็สร้างข้อผิดพลาดเช่นกัน
อีกครั้งหลักการเดียวกันนี้ใช้ได้ ที่น่าจะเป็น C# จะไม่อนุญาตให้แคสต์นี้เนื่องจากถึงแม้ว่า string
จะสืบทอดจาก object
แต่ List<T>
ไม่แสดง covariance ในลักษณะเดียวกัน Covariance อนุญาตให้ IEnumerable<Derived>
ถูกปฏิบัติเหมือน IEnumerable<Base>
แต่สิ่งนี้ไม่ขยายไปยังคอลเลกชันเช่นรายการ
คุณสามารถแคสต์ในทางกลับกันได้หรือไม่?
คุณอาจสงสัยว่าการแคสต์จาก List<object>
กลับไปเป็น List<string>
เป็นไปได้หรือไม่ ตัวอย่างเช่น:
List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol; // นี่ถูกต้องหรือไม่?
การดำเนินการนี้ไม่ตรงไปตรงมาด้วยเช่นกัน แม้ว่าการแคสต์นี้จะพยายามไปจากประเภททั่วไปไปยังประเภทเฉพาะ แต่มันถือว่ามีความเสี่ยง หาก ol
มีองค์ประกอบใด ๆ ที่ไม่ใช่สตริง นั่นจะทำให้เกิดข้อผิดพลาดในการแคสต์ที่ไม่ถูกต้อง
วิธีแก้ปัญหาทางเลือก
แม้ว่าคุณสมบัติความปลอดภัยของประเภทเหล่านี้อาจดูเหมือนเป็นการจำกัด แต่พวกมันมีความสำคัญต่อการรักษาความสมบูรณ์ภายในคอลเลกชันของคุณใน C# หากคุณต้องการเก็บประเภทที่แตกต่างกัน โปรดพิจารณาตัวเลือกต่อไปนี้:
- ใช้ polymorphism: เก็บวัตถุของประเภทพื้นฐานทั่วไปและนำวิธีการมาทำงานร่วมกับมัน
- ใช้ object lists: หากคุณต้องการผสมประเภท การใช้
List<object>
อาจเหมาะสม แต่ต้องการการจัดการที่ระมัดระวังระหว่างการดึงข้อมูลและการแคสต์
สรุป
โดยสรุปแล้ว คุณไม่สามารถเก็บ List<string>
ใน List<object>
ได้เนื่องจากกลไกความปลอดภัยประเภทที่เข้มงวดของ C# การเข้าใจแง่มุมนี้ของ generics จะช่วยป้องกันข้อผิดพลาดในขณะรันไทม์และรักษาความสมบูรณ์ของข้อมูลในแอปพลิเคชันของคุณ โดยการปฏิบัติตามหลักการเหล่านี้ คุณสามารถพัฒนาแอปพลิเคชัน C# ที่มีความแข็งแกร่งมากขึ้น
หากคุณมีคำถามหรือข้อคิดเห็นเพิ่มเติมเกี่ยวกับหัวข้อนี้ อย่าลังเลที่จะแบ่งปันความคิดเห็นของคุณ!