ควรใช้ IList เมื่อใดและเมื่อใดควรใช้รายการ


180

ฉันรู้ว่า IList เป็นอินเทอร์เฟซและรายการเป็นประเภทที่เป็นรูปธรรม แต่ฉันก็ยังไม่รู้ว่าจะใช้เมื่อใด สิ่งที่ฉันทำตอนนี้คือถ้าฉันไม่ต้องการวิธีเรียงลำดับหรือ FindAll ที่ฉันใช้อินเทอร์เฟซ ฉันถูกไหม? มีวิธีที่ดีกว่าในการตัดสินใจว่าจะใช้อินเตอร์เฟสหรือชนิดคอนกรีตเมื่อใด?


1
หากใครยังสงสัยฉันพบคำตอบที่ดีที่สุดที่นี่: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

คำตอบ:


175

มีกฎสองข้อที่ฉันปฏิบัติตาม:

  • ยอมรับประเภทพื้นฐานที่สุดที่จะใช้งานได้
  • ส่งคืนประเภทที่รวยที่สุดที่ผู้ใช้ของคุณต้องการ

ดังนั้นเมื่อเขียนฟังก์ชั่นหรือวิธีการที่ใช้คอลเลกชันเขียนมันจะไม่ใช้รายการ แต่ IList <T>, ICollection <T> หรือ IEnumerable <T> อินเทอร์เฟซทั่วไปจะยังคงทำงานได้แม้สำหรับรายการที่ต่างกันเนื่องจาก System.Object สามารถเป็น T ได้เช่นกัน การทำเช่นนี้จะช่วยให้คุณปวดหัวถ้าคุณตัดสินใจใช้ Stack หรือโครงสร้างข้อมูลอื่น ๆ หากทุกสิ่งที่คุณต้องทำในฟังก์ชั่นนั้นอยู่ข้างหน้า IEnumerable <T> นั้นเป็นสิ่งที่คุณควรจะถาม

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


43
คุณไม่ควรใช้ประเภทอินพุต / เอาท์พุตต่างกัน input และ output ประเภทควรทั้งเป็นชนิดพื้นฐานที่สุด (โดยเฉพาะอินเตอร์เฟซ) ที่จะรองรับความต้องการของลูกค้า Encapsulation อาศัยการบอกลูกค้าเพียงเล็กน้อยเกี่ยวกับการใช้งานคลาสของคุณให้มากที่สุด หากคุณส่งคืนรายการที่เป็นรูปธรรมคุณจะไม่สามารถเปลี่ยนเป็นประเภทที่ดีกว่าประเภทอื่นได้โดยไม่บังคับให้ลูกค้าทั้งหมดรวบรวมใหม่ / อัปเดต
Ash

11
ฉันไม่เห็นด้วยกับกฎ 2 ข้อ ... ฉันจะใช้ประเภทดั้งเดิมและพิเศษที่สุดเมื่อกลับมาในกรณีนี้ IList (IEnumarable ที่ดีกว่า) และคุณควรทำงานกับ List ในฟังก์ชันของคุณภายใน จากนั้นเมื่อคุณต้องการ "เพิ่ม" หรือ "เรียงลำดับ" จากนั้นใช้คอลเล็กชันหากต้องการมากกว่านั้นใช้รายการ ดังนั้นกฎที่ยากของฉันคือ: เริ่มต้นเสมอกับ IENumarable และหากคุณต้องการมากกว่านั้นให้ขยาย ...
ethem

2
เพื่อความสะดวกของคุณ "สองกฎ" มีชื่อ: หลักการความทนทาน (aka กฎหมายของพอส)
easoncxz

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

6
ฉันไม่เห็นด้วยอย่างยิ่งเกี่ยวกับจุดที่ 2 โดยเฉพาะอย่างยิ่งหากนี่อยู่ในขอบเขตบริการ / api การส่งคืนคอลเลกชันที่แก้ไขได้สามารถสร้างความประทับใจได้ว่าคอลเลกชันนั้นเป็น "สด" และวิธีการโทรเช่นAdd()และRemove()อาจมีผลกระทบมากกว่าแค่การรวบรวม การส่งคืนอินเทอร์เฟซแบบอ่านอย่างเดียวเช่นIEnumerableมักจะเป็นวิธีการในการดึงข้อมูล ผู้บริโภคของคุณสามารถฉายภาพให้เป็นประเภทที่สมบูรณ์ยิ่งขึ้นได้ตามต้องการ
STW

56

แนวทางของ Microsoft ที่ตรวจสอบโดย FxCop ไม่สนับสนุนการใช้ List <T> ใน API สาธารณะ - ชอบ IList <T>

บังเอิญตอนนี้ฉันเกือบจะประกาศอาร์เรย์หนึ่งมิติเป็น IList <T> ซึ่งหมายความว่าฉันสามารถใช้คุณสมบัติ IList <T> .Count มากกว่า Array.Length ตัวอย่างเช่น:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
ฉันชอบคำอธิบาย / ตัวอย่างมากที่สุด!
JonH

28

มีสิ่งสำคัญที่ผู้คนมักจะมองข้ามคือ:

คุณสามารถส่งอาเรย์ธรรมดาไปยังสิ่งที่ยอมรับIList<T>พารามิเตอร์จากนั้นคุณสามารถโทรIList.Add()และจะได้รับข้อยกเว้นรันไทม์:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

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

private void test(IList<int> list)
{
    list.Add(1);
}

หากคุณเรียกสิ่งต่อไปนี้คุณจะได้รับข้อยกเว้นรันไทม์:

int[] array = new int[0];
test(array);

สิ่งนี้เกิดขึ้นเนื่องจากการใช้อาร์เรย์ธรรมดาที่IList<T>ละเมิดหลักการทดแทน Liskov

ด้วยเหตุนี้ถ้าคุณกำลังเรียกIList<T>.Add()คุณอาจต้องการที่จะต้องพิจารณาต้องแทนList<T>IList<T>


นี่เป็นเรื่องจริงสำหรับทุกอินเทอร์เฟซ หากคุณต้องการที่จะติดตามด้วยอาร์กิวเมนต์ของคุณกว่าที่คุณสามารถโต้เถียงที่จะไม่ใช้อินเทอร์เฟซใด ๆ เลยเพราะการดำเนินการบางอย่างมันอาจจะโยน ในทางกลับกันหากคุณพิจารณาข้อเสนอแนะที่ได้รับจาก OP เพื่อเลือกList<T>มากกว่าIList<T>คุณควรตระหนักถึงเหตุผลที่IList<T>แนะนำ (ตัวอย่างเช่นblogs.msdn.microsoft.com/kcwalina/2005/09/26/ … )
Micha Wiedenmann

3
คำตอบ @MichaWiedenmann IList<T>.Add()ของที่นี่คือเฉพาะเมื่อคุณกำลังเรียกร้อง ฉันไม่ได้บอกว่าคุณไม่ควรใช้IList<T>- ฉันแค่ชี้ให้เห็นถึงข้อผิดพลาดที่เป็นไปได้ (ฉันมักจะใช้IEnumerable<T>หรือIReadOnlyList<T>หรือIReadOnlyCollection<T>ชอบIList<T>ถ้าฉันสามารถ)
แมทธิววัตสัน

24

ฉันเห็นด้วยกับคำแนะนำของ Lee ในการรับพารามิเตอร์ แต่ไม่กลับมา

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

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


22

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

IListการดำเนินการ
IList คุณควรใช้เมื่อคุณต้องการเข้าถึงดัชนีโดยการรวบรวมเพิ่มและลบองค์ประกอบ ฯลฯ ...IEnumerable
IList

รายการการดำเนินการ
ListIList


3
คำตอบที่ดีและชัดเจนซึ่งฉันทำเครื่องหมายว่ามีประโยชน์ อย่างไรก็ตามฉันจะเพิ่มว่าสำหรับนักพัฒนาส่วนใหญ่ส่วนใหญ่ความแตกต่างเล็กน้อยในขนาดโปรแกรมและประสิทธิภาพไม่น่ากังวลเกี่ยวกับ: หากมีข้อสงสัยให้ใช้รายการ
Graham Laight

9

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

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


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

5

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


4

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

ตัวอย่างเช่นสมมติว่าคุณมีPersonคลาสและGroupคลาส Groupเช่นมีคนจำนวนมากดังนั้นรายการที่นี่จะทำให้ความรู้สึก เมื่อฉันประกาศเป็นวัตถุรายการในGroupผมจะใช้และยกตัวอย่างว่ามันเป็นIList<Person>List

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

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


3
ทำไมไม่ทำให้มันเป็นเพียงรายการในสถานที่แรก? ฉันยังไม่เข้าใจว่าทำไมโบนัสที่คุณได้รับจากการทำให้มันเป็น IList จากนั้นในตัวสร้างที่คุณทำให้เป็นรายการ <>
chobo2

ฉันเห็นด้วยถ้าคุณกำลังสร้างวัตถุรายการ <T> อย่างชัดเจนแล้วคุณจะสูญเสียความได้เปรียบของอินเทอร์เฟซหรือไม่
The_Butcher

4

คุณมักจะใช้ประเภทที่ใช้งานทั่วไปได้ดีที่สุดในกรณีนี้ IList หรืออินเทอร์เฟซ IEnumerable ที่ดีกว่าเพื่อให้คุณสามารถสลับการใช้งานได้อย่างสะดวกในเวลาต่อมา

อย่างไรก็ตามใน NET 2.0 จะมีสิ่งที่น่ารำคาญ - IList ไม่ได้เรียง ()วิธีการ คุณสามารถใช้อะแดปเตอร์ที่ให้มาแทน:

ArrayList.Adapter(list).Sort()

2

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

หากรายการเป็นการนำไปปฏิบัติเพียงอย่างเดียวที่คุณจะใช้ในการรวบรวมบางรายการโปรดประกาศว่าเป็นการใช้งานรายการที่เป็นรูปธรรม


1

ในสถานการณ์ที่ฉันมักจะเจอฉันไม่ค่อยได้ใช้ IList โดยตรง

ฉันมักจะใช้มันเป็นอาร์กิวเมนต์ของวิธีการ

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

สิ่งนี้จะอนุญาตให้ฉันทำการประมวลผลทั่วไปบนอาเรย์ใด ๆ ในกรอบงาน. NET ยกเว้นว่าจะใช้ IEnumerable และไม่ใช่ IList ซึ่งบางครั้งก็เกิดขึ้น

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


1

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

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

ข้อแตกต่างคือ: IList เป็นส่วนต่อประสานและไม่สามารถสร้างอินสแตนซ์ได้ รายการเป็นคลาสและสามารถสร้างอินสแตนซ์ได้ มันหมายถึง:

IList<string> MyList = new IList<string>();

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