การส่งคืน 'IList' เทียบกับ 'ICollection' เทียบกับ 'Collection'


120

ฉันสับสนเกี่ยวกับประเภทคอลเลกชันที่ฉันควรส่งคืนจากเมธอดและคุณสมบัติ API สาธารณะของฉัน

คอลเลกชันที่ฉันมีในใจIList, และICollectionCollection

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



คำตอบ:


71

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

พิจารณาIEnumerable<T>อินเทอร์เฟซเป็นประเภทการส่งคืนด้วย หากผลลัพธ์เป็นเพียงการทำซ้ำผู้บริโภคไม่ต้องการมากกว่านั้น


25
แต่ยังพิจารณา ICollection <T> ซึ่งขยาย IEnumerable <T> เพื่อให้ยูทิลิตี้เพิ่มเติมรวมถึงคุณสมบัติ Count สิ่งนี้มีประโยชน์เนื่องจากคุณสามารถกำหนดความยาวของลำดับได้โดยไม่ต้องข้ามผ่านลำดับหลาย ๆ ครั้ง
retrodrone

11
@retrodrone: อันที่จริงแล้วการใช้Countวิธีนี้จะตรวจสอบประเภทจริงของคอลเลกชันดังนั้นจะใช้LengthหรือCountคุณสมบัติสำหรับบางประเภทที่รู้จักเช่นอาร์เรย์และICollectionแม้กระทั่งเมื่อคุณระบุเป็นIEnumerableไฟล์.
Guffa

8
@Guffa: มันอาจคุ้มค่าที่จะทราบว่าหากคลาสThing<T>ใช้IList<T>แต่ไม่ใช่แบบทั่วไปการICollectionเรียกIEnumerable<Cat>.Count()ใช้Thing<Cat>จะเร็ว แต่การโทรIEnumerable<Animal>.Count()จะช้า (เนื่องจากวิธีการขยายจะมองหาและไม่พบการใช้งานICollection<Cat>) หากการดำเนินการในชั้นเรียนที่ไม่ได้ทั่วไปICollectionแต่IEnumerable<Animal>.Countจะค้นหาและการใช้งานที่
supercat


8
ฉันไม่เห็นด้วย. เป็นการดีที่สุดที่จะส่งคืนประเภทที่ร่ำรวยที่สุดที่คุณมีเพื่อให้ลูกค้าสามารถใช้งานได้โดยตรงหากนั่นคือสิ่งที่พวกเขาต้องการ หากคุณมี List <> เหตุใดจึงส่งคืน IEnuerable <> เพียงเพื่อบังคับให้ผู้โทรเรียก ToList () โดยไม่จำเป็น
zumalifeguard

140

ICollection<T>เป็นอินเตอร์เฟซที่ exposes ความหมายเช่นคอลเลกชันAdd(), และRemove()Count

Collection<T>คือการนำICollection<T>อินเทอร์เฟซมาใช้อย่างเป็นรูปธรรม

IList<T>โดยพื้นฐานแล้วเป็นการICollection<T>เข้าถึงแบบสุ่มตามลำดับ

ในกรณีนี้คุณควรตัดสินใจว่าผลลัพธ์ของคุณต้องการความหมายของรายการหรือไม่เช่นการจัดทำดัชนีตามคำสั่ง (จากนั้นใช้IList<T>) หรือว่าคุณเพียงแค่ต้องส่งคืนผลลัพธ์ "กระเป๋า" ที่ไม่เรียงลำดับ (จากนั้นจึงใช้ICollection<T>)


5
Collection<T>การดำเนินการและไม่เพียง แต่IList<T> ICollection<T>
CodesInChaos

56
คอลเลกชันทั้งหมดใน. Net ได้รับคำสั่งเนื่องจากIEnumerable<T>ได้รับคำสั่ง สิ่งที่แตกต่างIList<T>จากICollection<T>คือมันมีสมาชิกที่จะทำงานกับดัชนี ตัวอย่างเช่นใช้list[5]งานได้ แต่collection[5]จะไม่รวบรวม
svick

5
IList<T>ฉันยังไม่เห็นว่าคอลเลกชันที่สั่งซื้อควรใช้ มีคอลเลกชันที่สั่งซื้อมากมายที่ไม่มี IList<T>เกี่ยวกับการเข้าถึงที่จัดทำดัชนีอย่างรวดเร็ว
CodesInChaos

4
@yoyo คลาสคอลเลกชัน. net และอินเทอร์เฟซได้รับการออกแบบมาไม่ดีและตั้งชื่อไม่ดี ฉันคงไม่คิดมากว่าทำไมถึงตั้งชื่อแบบนั้น
CodesInChaos

6
@svick: คอลเลกชันทั้งหมดได้รับคำสั่งเพราะIEnumerable<T>ได้รับคำสั่งหรือไม่? รับHashSet<T>- ใช้IEnumerable<T>แต่ไม่ได้รับคำสั่งอย่างชัดเจน กล่าวคือคุณไม่มีอิทธิพลต่อลำดับของรายการและคำสั่งซื้อนี้อาจเปลี่ยนแปลงได้ตลอดเวลาหากตารางแฮชภายในถูกจัดระเบียบใหม่
Olivier Jacot-Descombes

62

ความแตกต่างหลักระหว่างIList<T>และICollection<T>คือIList<T>ช่วยให้คุณสามารถเข้าถึงองค์ประกอบผ่านดัชนี IList<T>อธิบายประเภทคล้ายอาร์เรย์ องค์ประกอบใน an ICollection<T>สามารถเข้าถึงได้ผ่านการแจงนับเท่านั้น ทั้งสองอนุญาตให้แทรกและลบองค์ประกอบ

หากคุณต้องการเพียงแค่ระบุคอลเลกชันก็IEnumerable<T>เป็นที่ต้องการ มีข้อดีสองประการเหนือข้ออื่น ๆ :

  1. ไม่อนุญาตให้มีการเปลี่ยนแปลงในคอลเล็กชัน (แต่ไม่ใช่องค์ประกอบหากเป็นประเภทอ้างอิง)

  2. อนุญาตให้มีแหล่งที่มาที่หลากหลายมากที่สุดรวมถึงการแจงนับที่สร้างขึ้นตามอัลกอริทึมและไม่ใช่คอลเล็กชันเลย

Collection<T>เป็นคลาสพื้นฐานที่มีประโยชน์สำหรับผู้ใช้คอลเลกชัน หากคุณเปิดเผยในอินเทอร์เฟซ (API) คอลเล็กชันที่มีประโยชน์มากมายที่ไม่ได้มาจากคอลเล็กชันจะไม่รวมอยู่ด้วย


ข้อเสียอย่างหนึ่งIList<T>คืออาร์เรย์ใช้งานได้ แต่ไม่อนุญาตให้คุณเพิ่มหรือลบรายการ (กล่าวคือคุณไม่สามารถเปลี่ยนความยาวอาร์เรย์ได้) จะมีข้อยกเว้นเกิดขึ้นหากคุณเรียกIList<T>.Add(item)ใช้อาร์เรย์ สถานการณ์ค่อนข้างกลบเกลื่อนเนื่องจากIList<T>มีคุณสมบัติบูลีนIsReadOnlyที่คุณสามารถตรวจสอบได้ก่อนที่จะพยายามทำเช่นนั้น แต่ในสายตาของฉันนี้ยังคงเป็นข้อบกพร่องในการออกแบบห้องสมุด ดังนั้นฉันจึงใช้List<T>โดยตรงเมื่อจำเป็นต้องเพิ่มหรือลบรายการ


การวิเคราะห์รหัส (และ FxCop) ให้คำแนะนำว่าเมื่อกลับมาคอลเลกชันทั่วไปคอนกรีตหนึ่งต่อไปนี้ควรจะกลับ: Collection, หรือReadOnlyCollection KeyedCollectionและที่Listไม่ควรส่งคืน
DavidRR

@DavidRR: มักจะมีประโยชน์ในการส่งรายการไปยังวิธีการที่ต้องเพิ่มหรือลบรายการ หากคุณพิมพ์พารามิเตอร์เป็นIList<T>ไม่มีวิธีใดที่จะมั่นใจได้ว่าเมธอดจะไม่ถูกเรียกโดยมีอาร์เรย์เป็นพารามิเตอร์
Olivier Jacot-Descombes

7

IList<T>เป็นอินเทอร์เฟซพื้นฐานสำหรับรายการทั่วไปทั้งหมด เนื่องจากเป็นคอลเลกชันที่ได้รับคำสั่งการใช้งานจึงสามารถตัดสินใจเกี่ยวกับการสั่งซื้อได้ตั้งแต่ลำดับที่เรียงไปจนถึงลำดับการแทรก นอกจากนี้ยังIlistมีคุณสมบัติ Item ที่อนุญาตให้เมธอดอ่านและแก้ไขรายการในรายการตามดัชนี ทำให้สามารถแทรกลบค่าเข้า / ออกจากรายการที่ดัชนีตำแหน่งได้

นอกจากนี้ยังIList<T> : ICollection<T>มีวิธีการทั้งหมดจากICollection<T>ที่นี่สำหรับการใช้งาน

ICollection<T>เป็นอินเทอร์เฟซพื้นฐานสำหรับคอลเลกชันทั่วไปทั้งหมด กำหนดขนาดตัวนับและวิธีการซิงโครไนซ์ คุณสามารถเพิ่มหรือลบไอเท็มลงในคอลเลกชันได้ แต่คุณไม่สามารถเลือกตำแหน่งที่จะเกิดขึ้นได้เนื่องจากไม่มีคุณสมบัติดัชนี

Collection<T>ให้การดำเนินงานสำหรับIList<T>, และIListIReadOnlyList<T>

หากคุณใช้ประเภทอินเทอร์เฟซที่แคบกว่าเช่นICollection<T>แทนที่จะใช้IList<T>คุณจะป้องกันโค้ดของคุณจากการเปลี่ยนแปลงที่ไม่สมบูรณ์ หากคุณใช้ประเภทอินเทอร์เฟซที่กว้างขึ้นเช่นIList<T>คุณจะเสี่ยงต่อการทำลายโค้ดมากขึ้น

อ้างจากแหล่งที่มา ,

ICollection, ICollection<T>: คุณต้องการแก้ไขคอลเลกชันหรือคุณสนใจเกี่ยวกับขนาดของคอลเล็กชัน IList, IList<T>: คุณต้องการแก้ไขคอลเลกชันและคุณสนใจเกี่ยวกับลำดับและ / หรือการวางตำแหน่งขององค์ประกอบในคอลเลกชัน


5

การส่งคืนประเภทอินเทอร์เฟซนั้นเป็นเรื่องทั่วไปดังนั้น (ขาดข้อมูลเพิ่มเติมเกี่ยวกับกรณีการใช้งานเฉพาะของคุณ) ฉันจึงพึ่งพาสิ่งนั้น หากคุณต้องการแสดงการสนับสนุนการจัดทำดัชนีให้เลือกIList<T>มิฉะนั้นICollection<T>จะเพียงพอ IEnumerable<T>สุดท้ายหากคุณต้องการที่จะแสดงให้เห็นว่าประเภทที่ส่งกลับจะอ่านเพียงเลือก

และในกรณีที่คุณไม่เคยอ่านมาก่อน Brad Abrams และ Krzysztof Cwalina ได้เขียนหนังสือที่ยอดเยี่ยมเรื่อง "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" (คุณสามารถดาวน์โหลดไฟล์สรุปได้จากที่นี่ )


IEnumerable<T>อ่านอย่างเดียว? แน่นอน แต่เมื่อทำการปรับแต่งใหม่ในบริบทของที่เก็บแม้ว่าหลังจากเรียกใช้ ToList แล้วจะไม่ปลอดภัยสำหรับเธรด ปัญหาคือเมื่อแหล่งข้อมูลดั้งเดิมได้รับ GC แล้ว IEnumarble ToList () ไม่ทำงานในบริบทแบบมัลติเธรด มันทำงานบนเส้นตรงเนื่องจาก GC ถูกเรียกหลังจากที่คุณทำงาน แต่asyncตอนนี้ใช้เวลาหนึ่งวันใน 4.5+ IEnumarbale กำลังกลายเป็นอาการปวดบอล
Piotr Kula

-3

มีบางวิชาที่มาจากคำถามนี้:

  • อินเทอร์เฟซเทียบกับคลาส
  • คลาสใดที่เฉพาะเจาะจงจากหลายคลาสคอลเลกชันรายการอาร์เรย์
  • คลาสทั่วไปเทียบกับคอลเลกชันย่อย ("generics")

คุณอาจต้องการเน้นว่าเป็นObject Oriented API

อินเทอร์เฟซเทียบกับคลาส

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

และสิ้นสุดการออกแบบอินเทอร์เฟซที่ไม่ดีแทนที่จะเป็นการออกแบบคลาสที่ดีซึ่งในที่สุดก็สามารถโยกย้ายไปยังการออกแบบอินเทอร์เฟซที่ดีได้ ...

คุณจะเห็นอินเทอร์เฟซมากมายใน API แต่อย่ารีบเร่งถ้าคุณไม่ต้องการ

ในที่สุดคุณจะได้เรียนรู้วิธีใช้อินเทอร์เฟซกับโค้ดของคุณ

คลาสใดที่เฉพาะเจาะจงจากหลายคลาสคอลเลกชันรายการอาร์เรย์

มีหลายคลาสใน c # (dotnet) ที่สามารถแลกเปลี่ยนกันได้ ตามที่ได้กล่าวไปแล้วหากคุณต้องการบางสิ่งจากคลาสที่เฉพาะเจาะจงมากขึ้นเช่น "CanBeSortedClass" ให้กำหนดให้ชัดเจนใน API ของคุณ

ผู้ใช้ API ของคุณจำเป็นต้องทราบจริงๆหรือไม่ว่าชั้นเรียนของคุณสามารถจัดเรียงหรือใช้รูปแบบบางอย่างกับองค์ประกอบได้ จากนั้นใช้ "CanBeSortedClass" หรือ "ElementsCanBePaintedClass" หรือใช้ "GenericBrandClass"

มิฉะนั้นให้ใช้คลาสทั่วไป

คลาสคอลเลกชันทั่วไปเทียบกับคอลเลกชันย่อย ("generics")

คุณจะพบว่ามีคลาสที่มีองค์ประกอบอื่น ๆ และคุณสามารถระบุได้ว่าองค์ประกอบทั้งหมดควรเป็นประเภทเฉพาะ

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

ผู้ใช้ API ของคุณต้องการประเภทที่เฉพาะเจาะจงมากเหมือนกันสำหรับองค์ประกอบทั้งหมดหรือไม่?

List<WashingtonApple>ใช้สิ่งที่ต้องการ

ผู้ใช้ API ของคุณจำเป็นต้องมีประเภทที่เกี่ยวข้องหลายประเภทหรือไม่?

เปิดเผยList<Fruit>สำหรับ API ของคุณและใช้List<Orange> List<Banana>, List<Strawberry>ภายใน, ที่Orange, Bananaและเป็นลูกหลานจากStrawberryFruit

ผู้ใช้ API ของคุณต้องการคอลเล็กชันประเภททั่วไปหรือไม่?

การใช้งานListที่มีรายการทั้งหมดobject(s)

ไชโย


7
"หากคุณไม่มีประสบการณ์เกี่ยวกับอินเทอร์เฟซมากนักฉันขอแนะนำให้ติดคลาส" นี่ไม่ใช่คำแนะนำที่ดี นั่นเหมือนกับการบอกว่ามีบางอย่างที่คุณไม่รู้ให้อยู่ห่าง ๆ อินเทอร์เฟซมีประสิทธิภาพอย่างมากและสำรองแนวคิดของ "Program to abstractions vs. concretions" นอกจากนี้ยังมีประสิทธิภาพมากสำหรับการทดสอบหน่วยเนื่องจากความสามารถในการแลกเปลี่ยนประเภทต่างๆผ่านการล้อเลียน มีเหตุผลดีๆอีกมากมายที่จะใช้อินเทอร์เฟซนอกเหนือจากความคิดเห็นเหล่านี้ดังนั้นโปรดสำรวจด้วยวิธีการทั้งหมด
atconway

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

1
ฉันขอแนะนำให้เขียนโปรแกรมกับอินเทอร์เฟซเสมอ ช่วยให้โค้ดคู่กันอย่างหลวม ๆ
mac10688

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