ใน C # ทำไมไม่สามารถเก็บวัตถุ List <string> ในตัวแปร List <object> ได้


85

ดูเหมือนว่าวัตถุ List ไม่สามารถเก็บไว้ในตัวแปร List ใน C # และไม่สามารถแคสต์อย่างชัดเจนด้วยวิธีนั้นได้

List<string> sl = new List<string>();
List<object> ol;
ol = sl;

ผลลัพธ์ในไม่สามารถแปลงประเภทโดยปริยายSystem.Collections.Generic.List<string>เป็นSystem.Collections.Generic.List<object>

แล้ว ...

List<string> sl = new List<string>();
List<object> ol;
ol = (List<object>)sl;

ผลลัพธ์ในไม่สามารถแปลงประเภทSystem.Collections.Generic.List<string>เป็นSystem.Collections.Generic.List<object>

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


5
สิ่งนี้จะเปลี่ยนไปตาม C # 4.0 ดังนั้นคุณอาจต้องการค้นหาความแปรปรวนร่วมและความแตกต่าง จะช่วยให้สิ่งดังกล่าวอยู่ในลักษณะที่ปลอดภัย
John Leidegren

ซ้ำกันมากหรือน้อย: stackoverflow.com/questions/317335/…
Joel in Gö

7
John> C # 4 จะไม่อนุญาตสิ่งนี้ ลองนึกถึง ol.Add (วัตถุใหม่ ());
Guillaume

มีคำตอบโดย Jon Skeet ที่นี่: stackoverflow.com/a/2033921/1070906
JCH2k

คำตอบ:


39

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

ตามหมายเหตุด้านข้างฉันคิดว่ามันจะมีความสำคัญมากกว่าไม่ว่าคุณจะสามารถทำการร่ายย้อนกลับได้หรือไม่

List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol;

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

มีใครรู้หรือสามารถทดสอบได้ว่านักแสดงดังกล่าวถูกกฎหมายใน C # หรือไม่?


4
ฉันคิดว่าตัวอย่างของคุณประสบปัญหาเดียวกัน จะเกิดอะไรขึ้นถ้า ol มีบางอย่างที่ไม่ใช่สตริง? ปัญหาคือวิธีการบางอย่างในรายการจะใช้ได้ดีเช่นการเพิ่ม / แทรก แต่การทำซ้ำอาจเป็นปัญหาที่แท้จริง
Chris Ammerman

3
Eric Lippert มีบล็อกโพสต์ที่น่าสนใจมากมายเกี่ยวกับหัวข้อนี้: เหตุใดจึงสามารถเพิ่มความแปรปรวนร่วมและความขัดแย้งในวิธีการทั่วไปได้ แต่อาจไม่ได้ผลในแบบที่เราต้องการในระดับชั้นเรียน is.gd/3kQc
Chris Ammerman

@ChrisAmmerman: ถ้าolมีบางอย่างอยู่ในนั้นไม่ใช่สตริงฉันคิดว่าฉันคาดว่านักแสดงจะล้มเหลวในขณะรันไทม์ แต่ที่ที่คุณจะประสบปัญหาคือถ้านักแสดงทำสำเร็จแล้วมีบางอย่างเพิ่มเข้ามาolนั่นไม่ใช่สตริง เนื่องจากslอ้างอิงวัตถุเดียวกันตอนนี้ของคุณList<string>จะมีสตริงที่ไม่ใช่ Addเป็นปัญหาซึ่งผมเดาว่าทำไม justifies รหัสนี้จะไม่รวบรวม แต่มันจะรวบรวมถ้าคุณเปลี่ยนList<object> olไปซึ่งไม่ได้มีIEnumerable<object> ol Add(ฉันตรวจสอบสิ่งนี้ใน C # 4)
Tim Goodman

ด้วยการเปลี่ยนแปลงดังกล่าวคอมไพล์ แต่พ่นInvalidCastExceptionเนื่องจากประเภทรันไทม์olยังคงList<object>อยู่
Tim Goodman

36

หากคุณใช้. NET 3.5 ให้ดูที่วิธี Enumerable.Cast เป็นวิธีการขยายเพื่อให้คุณสามารถเรียกใช้โดยตรงในรายการ

List<string> sl = new List<string>();
IEnumerable<object> ol;
ol = sl.Cast<object>();

ไม่ใช่สิ่งที่คุณขอ แต่ควรทำตามเคล็ดลับ

แก้ไข: ตามที่ Zooba ระบุไว้คุณสามารถโทรหา ol ToList () เพื่อรับรายการ


14

คุณไม่สามารถแคสต์ระหว่างประเภททั่วไปด้วยพารามิเตอร์ประเภทต่างๆ ประเภทสามัญเฉพาะไม่ได้เป็นส่วนหนึ่งของโครงสร้างมรดกเดียวกันและเป็นประเภทที่ไม่เกี่ยวข้องกัน

การทำ pre-NET 3.5 นี้:

List<string> sl = new List<string>();
// Add strings to sl

List<object> ol = new List<object>();

foreach(string s in sl)
{
    ol.Add((object)s);  // The cast is performed implicitly even if omitted
}

ใช้ Linq:

var sl = new List<string>();
// Add strings to sl

var ol = new List<object>(sl.Cast<object>());

// OR
var ol = sl.Cast<object>().ToList();

// OR (note that the cast to object here is required)
var ol = sl.Select(s => (object)s).ToList();

11

เหตุผลก็คือคลาสทั่วไปเช่นList<>นั้นสำหรับวัตถุประสงค์ส่วนใหญ่ได้รับการปฏิบัติจากภายนอกเหมือนคลาสปกติ เช่นเมื่อคุณพูดว่าList<string>()คอมไพเลอร์พูดListString()(ซึ่งมีสตริง) [ชาวบ้านด้านเทคนิค: นี่คือสิ่งที่เกิดขึ้นในภาษาอังกฤษ - ภาษาอังกฤษแบบธรรมดา ๆ ]

ดังนั้นเห็นได้ชัดว่าคอมไพเลอร์ไม่ฉลาดพอที่จะแปลง ListString เป็น ListObject โดยการแคสต์รายการของคอลเลกชันภายใน

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


6

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

คุณอาจต้องการลองใช้รหัส ff แทน:

List<string> sl = new List<string>();
//populate sl
List<object> ol = new List<object>(sl);

หรือ:

List<object> ol = new List<object>();
ol.AddRange(sl);

ol จะ (ในทางทฤษฎี) คัดลอกเนื้อหาทั้งหมดของ sl โดยไม่มีปัญหา





1

นั่นเป็นความจริงที่ว่าคุณจะไม่พยายามใส่ "วัตถุ" แปลก ๆ ในตัวแปรรายการ "ol" ของคุณ (ตามที่List<object>ดูเหมือนจะอนุญาต) - เพราะโค้ดของคุณจะขัดข้อง (เนื่องจากรายการเป็นList<string>และจะยอมรับเฉพาะอ็อบเจ็กต์ประเภทสตริงเท่านั้น ). นั่นเป็นเหตุผลที่คุณไม่สามารถแคสต์ตัวแปรของคุณเป็นข้อกำหนดทั่วไปได้

ใน Java เป็นอีกทางหนึ่งคุณไม่มี generics และทุกอย่างเป็น List of object ในขณะรันไทม์แทนและคุณสามารถยัดวัตถุแปลก ๆ ในรายการที่พิมพ์อย่างเคร่งครัดตามที่คาดคะเนได้ ค้นหา "Reified generics" เพื่อดูการอภิปรายปัญหาของ java ในวงกว้าง ...


1

ไม่รองรับความแปรปรวนร่วมดังกล่าวกับยาชื่อสามัญ แต่คุณสามารถทำได้ด้วยอาร์เรย์:

object[] a = new string[] {"spam", "eggs"};

C # ทำการตรวจสอบรันไทม์เพื่อป้องกันไม่ให้คุณใส่พูดintลงในaไฟล์.


0

นี่คืออีกหนึ่งโซลูชัน pre -.NET 3.5 สำหรับ IList ที่สามารถแคสต์เนื้อหาได้โดยปริยาย

public IList<B> ConvertIList<D, B>(IList<D> list) where D : B
{
    List<B> newList = new List<B>();

    foreach (D item in list)
    {
        newList.Add(item);
    }

    return newList;
}

(ตามตัวอย่างของ Zooba)


0

ฉันมี:

private List<Leerling> Leerlingen = new List<Leerling>();

และฉันจะเติมข้อมูลที่รวบรวมในList<object> สิ่งที่ได้ผลสำหรับฉันในที่สุดก็คือสิ่งนี้:

Leerlingen = (List<Leerling>)_DeserialiseerLeerlingen._TeSerialiserenObjecten.Cast<Leerling>();

.Castไปยังประเภทที่คุณต้องการได้รับIEnumerableจากประเภทนั้นจากนั้นพิมพ์ตามIEnemuerableที่List<>คุณต้องการ


0

ขอบคุณความคิดเห็นก่อนหน้านี้ฉันพบสองวิธีในการค้นหา รายการแรกคือการรับรายการสตริงขององค์ประกอบจากนั้นส่งไปยังรายการวัตถุที่ไม่สามารถคำนวณได้ของ IE:

IEnumerable<object> ob;
List<string> st = new List<string>();
ob = st.Cast<object>();

และอย่างที่สองคือการหลีกเลี่ยงประเภทอ็อบเจ็กต์ IEnumerable เพียงแค่แคสต์สตริงเป็นประเภทอ็อบเจ็กต์จากนั้นใช้ฟังก์ชัน "toList ()" ในประโยคเดียวกัน:

List<string> st = new List<string>();
List<object> ob = st.Cast<object>().ToList();

ฉันชอบวิธีที่สองมากกว่า ฉันหวังว่านี่จะช่วยได้.


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