ฉันควรส่งคืน IEnumerable <T> แทน IList <T> หรือไม่


98

เมื่อฉันเขียน DAL หรือรหัสอื่นที่ส่งคืนชุดของรายการฉันควรทำคำสั่งส่งคืนเสมอ:

public IEnumerable<FooBar> GetRecentItems()

หรือ

public IList<FooBar> GetRecentItems()

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


1
มันคือ List <T> หรือ IList <T> ที่คุณกำลังถาม? ชื่อและคำถามบอกว่าคนละสิ่ง ...
Fredrik Mörk

เมื่อเป็นไปได้ส่วนต่อประสานผู้ใช้ IEnumerable หรือ Ilist instaead ประเภท Concerete
Usman Masood

2
ฉันส่งคืนคอลเลกชันเป็นรายการ <T> ฉันไม่เห็นความจำเป็นที่จะต้องส่งคืน IEnumberable <T> เนื่องจากคุณสามารถดึงข้อมูลนั้นจากรายการ <T>
Chuck Conway


เป็นไปได้ที่ซ้ำกันของienumerablet-as-return-type
nawfal

คำตอบ:


45

ขึ้นอยู่กับสาเหตุที่คุณใช้อินเทอร์เฟซเฉพาะนั้นจริงๆ

ตัวอย่างเช่นIList<T>มีหลายวิธีที่ไม่มีอยู่ในIEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

และคุณสมบัติ:

  • T this[int index] { get; set; }

IList<T>หากคุณต้องการวิธีการเหล่านี้ในทางใดทางหนึ่งแล้วโดยทั้งหมดกลับ

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


2
@Jon FDG แนะนำให้ใช้ Collection <T> หรือ ReadOnlyCollection <T> เป็นค่าส่งคืนสำหรับประเภทคอลเลกชันดูคำตอบของฉัน
Sam Saffron

คุณพูดไม่ชัดว่าคุณหมายถึงอะไรในประโยคสุดท้ายเมื่อคุณพูดว่า "มันจะช่วย CLR" จะช่วยอะไรได้บ้างโดยใช้ IEnumerable vs. IList คุณช่วยให้ชัดเจนขึ้นได้ไหม
PositiveGuy

1
@CoffeeAddict สามปีหลังจากคำตอบนี้ฉันคิดว่าคุณพูดถูก - ส่วนสุดท้ายนั้นคลุมเครือ หากวิธีการที่คาดว่า IList <T> เป็นพารามิเตอร์ได้รับ IEnumerable <T> IEnumerable จะต้องถูกรวมไว้ใน List <T> ใหม่หรือตัวดำเนินการ IList <T> อื่น ๆ ด้วยตนเองและงานนั้นจะไม่ทำโดย CLR สำหรับคุณ ตรงกันข้าม - วิธีที่คาดว่า IEnumerable <T> จะได้รับ IList <T> อาจต้องทำการแกะกล่อง แต่ในการมองย้อนกลับอาจไม่จำเป็นต้องทำเพราะ IList <T> ใช้ IEnumerable <T>
Jon Limjap

69

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

เหตุผลนี้เป็นที่ต้องการง่าย ๆIListคือIListไม่แจ้งให้ผู้โทรทราบว่าเป็นแบบอ่านอย่างเดียวหรือไม่

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

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

โดยส่วนตัวแล้วสำหรับ ORM ฉันอาจจะยึดติดกับCollection<T>ค่าตอบแทนของฉัน


14
แนวทางปฏิบัติสำหรับคอลเลกชันที่มีรายละเอียดมากขึ้นของ Dos และ Donts
Chaquotay

27

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

หลวมตัวในสิ่งที่คุณต้องการและชัดเจนในสิ่งที่คุณให้


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

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

1
ฉันคิดว่าฉันเห็นด้วยกับการมีพารามิเตอร์ทั่วไปมากกว่า แต่คุณมีเหตุผลอะไรในการส่งคืนสิ่งที่ไม่ธรรมดา
Dave Cousineau

7
โอเคให้ฉันลองวิธีอื่น จะทิ้งข้อมูลไปทำไม? หากคุณสนใจเฉพาะผลลัพธ์ที่เป็น IEnumerable <T> การรู้ว่าเป็น IList <T> หรือไม่ ไม่มันไม่ได้ อาจเป็นข้อมูลที่ไม่จำเป็นในบางกรณี แต่ก็ไม่เป็นอันตรายต่อคุณ ตอนนี้เพื่อประโยชน์ หากคุณส่งคืน List หรือ IList ฉันสามารถบอกได้ทันทีว่ามีการเรียกคอลเล็กชันไปแล้วสิ่งที่ฉันไม่สามารถทราบได้ด้วย IEnumerable นี่อาจเป็นข้อมูลที่มีประโยชน์หรือไม่ก็ได้ แต่ทำไมคุณถึงทิ้งข้อมูลไปอีกครั้ง หากคุณทราบข้อมูลเพิ่มเติมเกี่ยวกับบางสิ่งบางอย่างให้ส่งต่อไป
Mel

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

23

ขึ้นอยู่กับ ...

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

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

ฉันมักจะแนะนำให้ส่งคืนประเภทที่ได้รับน้อยที่สุดซึ่งมีการดำเนินการทั้งหมดที่ผู้ใช้ของคุณต้องการ ...


คำตอบทั่วไปที่ดีเนื่องจากใช้กับวิธีการอื่น ๆ ด้วย
user420667

1
ฉันรู้ว่าคำตอบนี้เก่ามาก ... แต่ดูเหมือนว่าจะขัดแย้งกับเอกสาร: "หากอินเทอร์เฟซ IDictionary <TKey, TValue> หรืออินเทอร์เฟซ IList <T> ไม่ตรงตามข้อกำหนดของคอลเล็กชันที่ต้องการให้ได้คลาสคอลเลกชันใหม่จาก อินเทอร์เฟซ ICollection <T> แทนเพื่อความยืดหยุ่นที่มากขึ้น "ดูเหมือนจะบ่งบอกว่าควรเลือกประเภทที่ได้รับมากกว่า ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK

14

สิ่งหนึ่งที่ควรพิจารณาคือหากคุณใช้คำสั่ง LINQ ที่รอการตัดบัญชีเพื่อสร้างการIEnumerable<T>โทรของ.ToList()คุณก่อนที่คุณจะกลับจากวิธีการของคุณหมายความว่ารายการของคุณอาจถูกทำซ้ำสองครั้ง - หนึ่งครั้งเพื่อสร้างรายการและอีกครั้งเมื่อผู้โทรวนซ้ำ กรองหรือแปลงค่าที่ส่งคืนของคุณ ในทางปฏิบัติฉันชอบที่จะหลีกเลี่ยงการแปลงผลลัพธ์ของ LINQ-to-Objects เป็นรายการหรือพจนานุกรมที่เป็นรูปธรรมจนกว่าฉันจะต้องทำ หากผู้โทรของฉันต้องการรายชื่อนั่นเป็นวิธีง่ายๆเพียงวิธีเดียวในการโทรออกไป - ฉันไม่จำเป็นต้องตัดสินใจสำหรับพวกเขาและนั่นทำให้รหัสของฉันมีประสิทธิภาพมากขึ้นเล็กน้อยในกรณีที่ผู้โทรกำลังทำ foreach


@ Joel Mueller ฉันมักจะเรียก ToList () กับพวกเขาอยู่แล้ว โดยทั่วไปฉันไม่ชอบเปิดเผย IQueryable กับโครงการที่เหลือของฉัน
KingNestor

4
ฉันกำลังอ้างถึง LINQ-to-Objects มากขึ้นโดยที่ IQueryable มักจะไม่ใส่รูปภาพ เมื่อมีฐานข้อมูลเข้ามาเกี่ยวข้อง ToList () จะมีความจำเป็นมากขึ้นเพราะไม่เช่นนั้นคุณอาจเสี่ยงที่จะปิดการเชื่อมต่อก่อนที่จะทำซ้ำซึ่งไม่ได้ผล อย่างไรก็ตามเมื่อนั่นไม่ใช่ปัญหามันค่อนข้างง่ายที่จะเปิดเผย IQueryable เป็น IEnumerable โดยไม่ต้องบังคับให้ทำซ้ำเพิ่มเติมเมื่อคุณต้องการซ่อน IQueryable
Joel Mueller

9

List<T>มีคุณสมบัติอื่น ๆ อีกมากมายสำหรับรหัสการเรียกเช่นการแก้ไขวัตถุที่ส่งคืนและการเข้าถึงโดยดัชนี ดังนั้นคำถามจึงลดลงไปที่: ในกรณีการใช้งานเฉพาะของแอปพลิเคชันของคุณคุณต้องการสนับสนุนการใช้งานดังกล่าวหรือไม่ (น่าจะเป็นการคืนคอลเลคชันที่สร้างขึ้นใหม่!) เพื่อความสะดวกของผู้โทรหรือคุณต้องการความเร็วสำหรับกรณีง่ายๆเมื่อทั้งหมด ความต้องการของผู้โทรคือการวนซ้ำคอลเลกชันและคุณสามารถส่งคืนการอ้างอิงไปยังคอลเลกชันที่แท้จริงได้อย่างปลอดภัยโดยไม่ต้องกลัวว่าสิ่งนี้จะเปลี่ยนไปอย่างผิดพลาด ฯลฯ ?

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


"ส่งคืนการอ้างอิงไปยังคอลเลคชันที่แท้จริงอย่างปลอดภัยโดยไม่ต้องกลัวว่าจะถูกเปลี่ยนแปลงอย่างผิดพลาด" - แม้ว่าคุณจะส่งคืน IEnumerable <T> แต่พวกเขาไม่สามารถส่งกลับไปที่ List <T> และเปลี่ยนแปลงได้หรือไม่?
Kobi

ไม่ใช่ทุก IEnumarable <T> ที่เป็น List <T> หากอ็อบเจ็กต์ที่ส่งคืนไม่ใช่ประเภทที่สืบทอดมาจาก List <T> หรือใช้ IList <T> สิ่งนี้จะส่งผลให้ InvalidCastException
lowglider

2
รายการ <T> มีปัญหาที่จะล็อกคุณไว้ในคอลเล็กชันการใช้งานเฉพาะ <T> หรือ ReadOnlyCollection <T> เป็นที่ต้องการ
Sam Saffron

4

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

IEnumerable ไม่มีประสิทธิภาพในการนับองค์ประกอบ

หากคอลเลกชันมีวัตถุประสงค์เพื่ออ่านอย่างเดียวหรือการปรับเปลี่ยนคอลเล็กชันถูกควบคุมโดยการParentส่งคืนIListjust for Countนั้นไม่ใช่ความคิดที่ดี

ใน Linq มีCount()วิธีการขยายIEnumerable<T>ซึ่งภายใน CLR จะลัดไปยัง.Countประเภทที่อยู่ภายใต้IListดังนั้นความแตกต่างของประสิทธิภาพจึงไม่สำคัญ

โดยทั่วไปแล้วฉันรู้สึกว่า (ความเห็น) เป็นแนวทางปฏิบัติที่ดีกว่าในการส่งคืน IEnumerable หากเป็นไปได้หากคุณจำเป็นต้องทำการเพิ่มเติมจากนั้นเพิ่มวิธีการเหล่านี้ไปยังคลาสหลักมิฉะนั้นผู้บริโภคจะจัดการคอลเล็กชันภายใน Model ที่ละเมิดหลักการเช่นmanufacturer.Models.Add(model)ละเมิดกฎหมายของ demeter. แน่นอนว่านี่เป็นเพียงแนวทางและไม่ใช่กฎที่ยากและรวดเร็ว แต่จนกว่าคุณจะเข้าใจถึงการบังคับใช้อย่างเต็มที่การทำตามแบบสุ่มสี่สุ่มห้าจะดีกว่าการไม่ปฏิบัติตามเลย

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(หมายเหตุ: หากใช้ nNHibernate คุณอาจต้องแมปกับ IList ส่วนตัวโดยใช้ตัวเข้าถึงอื่น)


2

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

ในกรณีของค่าที่ส่งคืนจะยากกว่า ผู้โทรของคุณคาดหวังอะไร หากคุณส่งคืน IE ที่นับได้เขาจะไม่รู้เบื้องต้นว่าเขาสามารถสร้าง IList จากมันได้ แต่ถ้าคุณส่งคืน IList เขาจะรู้ว่าเขาสามารถทำซ้ำได้ ดังนั้นคุณต้องคำนึงถึงสิ่งที่ผู้โทรของคุณจะทำกับข้อมูล ฟังก์ชันที่ผู้โทรของคุณต้องการ / คาดหวังคือสิ่งที่ควรควบคุมเมื่อตัดสินใจว่าจะส่งคืนอะไร


0

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


0

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

แต่ถ้าคุณต้องการนับรายการอย่าลืมว่ามีชั้นระหว่าง IEnumerable และ IList อื่น - ICollection


0

ฉันอาจจะไม่สบายใจที่นี่เห็นว่าตอนนี้ยังไม่มีใครแนะนำ แต่ทำไมคุณไม่ส่งคืน(I)Collection<T>ล่ะ?

จากสิ่งที่ฉันจำCollection<T>ได้เป็นประเภทผลตอบแทนที่ต้องการมากกว่าList<T>เพราะมันเป็นนามธรรมที่นำไปใช้งาน พวกเขาทั้งหมดนำไปใช้IEnumerableแต่นั่นฟังดูต่ำเกินไปสำหรับงาน


0

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

IEnumerable ไม่มีประสิทธิภาพในการนับองค์ประกอบหรือรับองค์ประกอบเฉพาะในคอลเล็กชัน

List เป็นคอลเล็กชันที่เหมาะอย่างยิ่งสำหรับการค้นหาองค์ประกอบเฉพาะเพิ่มองค์ประกอบหรือลบออกได้ง่าย

โดยทั่วไปฉันพยายามที่จะใช้ Listเมื่อเป็นไปได้เพราะจะทำให้ฉันมีความยืดหยุ่นมากขึ้น

ใช้ List<FooBar> getRecentItems() มากกว่า IList<FooBar> GetRecentItems()


0

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

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

โปรดจำไว้ว่าการย้าย UP ไปยังคอลเล็กชันจาก IEnumerable ในอินเทอร์เฟซจะใช้งานได้การย้ายลงไปที่ IEnumerable จากคอลเล็กชันจะทำลายโค้ดที่มีอยู่

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


0

TL; ดร; - สรุป

  • หากคุณพัฒนาซอฟต์แวร์ภายในให้ใช้ประเภทเฉพาะ (Like List ) สำหรับค่าที่ส่งคืนและประเภททั่วไปที่สุดสำหรับพารามิเตอร์อินพุตแม้ในกรณีของคอลเล็กชัน
  • หากเมธอดเป็นส่วนหนึ่งของ API สาธารณะของไลบรารีที่แจกจ่ายต่อได้ให้ใช้อินเทอร์เฟซแทนประเภทการรวบรวมคอนกรีตเพื่อแนะนำทั้งค่าส่งคืนและพารามิเตอร์อินพุต
  • หากเมธอดส่งคืนคอลเล็กชันแบบอ่านอย่างเดียวให้แสดงว่าโดยใช้IReadOnlyListหรือIReadOnlyCollectionเป็นชนิดค่าที่ส่งคืน

มากกว่า

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