การรวบรวม <T> กับรายการ <T> คุณควรใช้อะไรในส่วนต่อประสานของคุณ


162

รหัสดูเหมือนว่าด้านล่าง:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

เมื่อฉันเรียกใช้การวิเคราะห์รหัสฉันได้รับคำแนะนำต่อไปนี้

คำเตือน 3 CA1002: Microsoft.Design: เปลี่ยน 'รายการ' ใน 'IMyClass.GetList ()' เพื่อใช้ Collection, ReadOnlyCollection หรือ KeyedCollection

ฉันจะแก้ไขสิ่งนี้และวิธีปฏิบัติที่ดีที่นี่ได้อย่างไร

คำตอบ:


234

ในการตอบคำถาม "ทำไม" ถึงเป็นสาเหตุที่ไม่List<T>เหตุผลคือการพิสูจน์ในอนาคตและความเรียบง่ายของ API

อนาคตพิสูจน์อักษร

List<T>ไม่ได้ออกแบบมาให้สามารถขยายได้อย่างง่ายดายโดยการ subclassing มันถูกออกแบบมาให้รวดเร็วสำหรับการใช้งานภายใน คุณจะสังเกตเห็นว่าวิธีการนั้นไม่ใช่แบบเสมือนดังนั้นจึงไม่สามารถเขียนทับได้และไม่มี hooks ในAdd/ Insert/ Removeการทำงาน

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

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

API Simplicity

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

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


1
คำตอบนี้ตายไปแล้ว อ่านที่ดีในเรื่องอื่นสามารถพบได้ที่นี่: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo

7
ฉันเห็นคะแนนแรกของคุณแล้ว แต่ฉันไม่รู้ว่าฉันเห็นด้วยหรือไม่ในส่วนความเรียบง่าย API ของคุณ
Boris Callens

stackoverflow.com/a/398988/2632991นี่คือโพสต์ที่ดีมากเช่นกันเกี่ยวกับความแตกต่างระหว่างคอลเลกชันและรายการคืออะไร
El Mac

2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> ตายแล้ว เรามีข้อมูลที่ใหม่กว่าสำหรับสิ่งนี้หรือไม่?
Machado

1
ลิงค์ที่ใช้งานได้: blogs.msdn.microsoft.com/kcwalina/2005/09/26/…
ofthelit

50

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


1
IList จะขยายส่วนต่อประสาน ICollection หรือไม่
bovium

8
@ จอน: ฉันรู้ว่ามันเก่า แต่คุณสามารถแสดงความคิดเห็นในสิ่งที่ Krzysztof พูดได้ที่blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? ความคิดเห็นของเขาโดยเฉพาะWe recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 ดูเหมือนจะสอดคล้องกับความคิดเห็นของ Krzysztof ฉันไม่สามารถจินตนาการได้ว่าเหตุใดจึงแนะนำให้มีการรวบรวมที่เป็นรูปธรรมแทนที่จะใช้อินเทอร์เฟซ
Nelson Rothermel

3
@ เนลสัน: มันยากที่คุณต้องการให้ผู้โทรต้องผ่านรายการที่ไม่เปลี่ยนรูป แต่ก็มีเหตุผลที่จะต้องส่งคืนเพื่อให้พวกเขารู้ว่ามันไม่แน่นอน ไม่แน่ใจเกี่ยวกับคอลเล็กชันอื่น ๆ จะดีกว่าถ้ามีรายละเอียดเพิ่มเติม
Jon Skeet

2
ไม่ใช่สำหรับกรณีเฉพาะ โดยทั่วไปแล้วReadOnlyCollection<T>จะไม่เหมาะสมสำหรับการป้อนข้อมูล ในทำนองเดียวกันIList<T>อินพุตกล่าวว่า "ฉันต้องการ Sort () หรือสมาชิกคนอื่น ๆ ที่ IList มี" ซึ่งไม่สมเหตุสมผลสำหรับผลลัพธ์ แต่สิ่งที่ฉันหมายถึงคือทำไมจะICollection<T>แนะนำให้เป็นอินพุทและCollection<T>เอาท์พุท ทำไมไม่ใช้ICollection<T>เป็นเอาท์พุทตามที่คุณแนะนำ?
เนลสันโรเธอร์เมล

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

3

ฉันไม่คิดว่าจะมีใครตอบคำว่า "ทำไม" ตอนนี้ ... ไปเลย เหตุผล "ทำไม" คุณ "ควร" ใช้Collection<T>แทนการList<T>เป็นเพราะถ้าคุณเปิดเผย a List<T>แล้วทุกคนที่ได้รับการเข้าถึงวัตถุของคุณสามารถแก้ไขรายการในรายการ โดยที่Collection<T>ควรจะระบุว่าคุณกำลังสร้างวิธี "Add", "Remove" ของคุณเอง

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

หากคุณมีอาร์เรย์สาธารณะเช่น:

public int[] MyIntegers { get; }

คุณจะคิดว่าเพราะมีเพียง accessor "รับ" ที่ไม่มีใครสามารถยุ่งกับค่า แต่นั่นไม่เป็นความจริง ทุกคนสามารถเปลี่ยนค่าภายในที่นั่นเช่นนี้

someObject.MyIngegers[3] = 12345;

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


"หากคุณส่งคืนรายการ <T> ไปยังรหัสลูกค้าคุณจะไม่สามารถรับการแจ้งเตือนได้เมื่อรหัสลูกค้าแก้ไขการรวบรวม" - FX COP ... ดู " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... ว้าวดูเหมือนว่าฉันไม่ได้ปิดฐานหลังจาก :) ทั้งหมด
ทิโมธี Khouri

ว้าว - ปีต่อมาฉันเห็นว่าลิงก์ที่ฉันวางในความคิดเห็นด้านบนมีการเสนอราคาไว้ที่ส่วนท้ายของมันดังนั้นมันจึงใช้งานไม่ได้ ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri

2

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

ไม่ใช่วิธีปฏิบัติที่ดีที่จะให้วัตถุอื่น (หรือบุคคล) แก้ไขสถานะของวัตถุของคุณโดยตรง คิดว่าทรัพย์สิน getters / setters

คอลเลกชัน -> สำหรับคอลเลกชันปกติ
ReadOnlyCollection -> สำหรับคอลเลกชันที่ไม่ควรแก้ไข
KeyedCollection -> เมื่อคุณต้องการพจนานุกรมแทน

วิธีแก้ไขมันขึ้นอยู่กับสิ่งที่คุณต้องการให้คลาสของคุณทำและวัตถุประสงค์ของเมธอด GetList () คุณสามารถทำอย่างละเอียด?


1
แต่คอลเล็กชัน <T> และ KeyedCollection <,> ไม่ได้เป็นแบบอ่านอย่างเดียว
nawfal

@nawfal แล้วฉันจะบอกว่ามันอยู่ที่ไหน?
chakrit

1
คุณไม่อนุญาตให้วัตถุอื่น ๆ (หรือคน) แก้ไขสถานะของวัตถุของคุณโดยตรงและรายการประเภทคอลเลกชัน 3 แบริ่งReadOnlyCollectionอีกสองคนไม่เชื่อฟัง
nawfal

นั่นหมายถึง "การปฏิบัติที่ดี" โปรดบริบทที่เหมาะสม รายการด้านล่างนี้เพียงระบุความต้องการพื้นฐานของประเภทดังกล่าวเนื่องจาก OP ต้องการเข้าใจคำเตือน จากนั้นฉันก็ถามเขาเกี่ยวกับจุดประสงค์ของGetList()การช่วยเหลือเขาให้ถูกต้องมากขึ้น
chakrit

1
ตกลงฉันเข้าใจส่วนนั้น แต่ก็ยังไม่มีเหตุผลที่เหมาะสมสำหรับฉัน คุณบอกว่าCollection<T>ช่วยในการทำให้การใช้งานภายในเป็นนามธรรมและป้องกันการจัดการรายชื่อภายในโดยตรง อย่างไร? Collection<T>เป็นเพียง wrapper ที่ทำงานบนอินสแตนซ์เดียวกันที่ส่งผ่าน มันเป็นคอลเลกชันแบบไดนามิกที่มีความหมายสำหรับการสืบทอดไม่มีอะไรอื่น (คำตอบของเกร็กมีความเกี่ยวข้องมากกว่าที่นี่)
nawfal

1

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


1

บางสิ่งบางอย่างที่จะเพิ่มแม้ว่ามันจะเป็นเวลานานนับตั้งแต่ถูกถาม

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

ในกรณีที่รหัสภายนอกมีการเข้าถึงคอลเล็กชันของคุณคุณอาจไม่สามารถควบคุมได้ว่าเมื่อใดที่รายการนั้นถูกเพิ่มหรือลบ ดังนั้นจึงCollection<T>มีวิธีที่จะรู้ว่าเมื่อใดที่รายการของคุณได้รับการแก้ไข


0

ฉันไม่เห็นปัญหาใด ๆ กับการส่งคืนสิ่งที่ต้องการ

this.InternalData.Filter(crteria).ToList();

ถ้าผมกลับตัดการเชื่อมต่อ สำเนาของข้อมูลภายในหรือผลแฝดของการค้นหาข้อมูล - ฉันสามารถกลับมาList<TItem>โดยไม่ต้องเปิดเผยรายละเอียดใด ๆ ดำเนินงานและการอนุญาตให้มีการใช้ข้อมูลที่ส่งกลับในวิธีที่สะดวก

แต่สิ่งนี้ขึ้นอยู่กับประเภทของผู้บริโภคที่ฉันคาดหวัง - ถ้านี่เป็นตารางข้อมูลที่ฉันชอบที่จะกลับมาIEnumerable<TItem> ซึ่งจะเป็นรายการที่คัดลอกไว้ในกรณีส่วนใหญ่ :)


-1

คลาส Collection เป็นเพียงคลาส wrapper รอบคอลเลกชันอื่น ๆ เพื่อซ่อนรายละเอียดการใช้งานและคุณสมบัติอื่น ๆ ฉันคิดว่าสิ่งนี้เกี่ยวข้องกับการซ่อนรูปแบบการเข้ารหัสในภาษาเชิงวัตถุ

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

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);

1
ขออภัยนั่นเป็นคำที่พิมพ์ผิด ฉัน menat Collection <MyClass> ฉันรอคอยที่จะได้รับ C # 4 และความแปรปรวนร่วมทั่วไป btw!
Tamas Czinege

การห่อCollection<T>ป้องกันไม่ได้เป็นของตัวเองหรือของสะสมเท่าที่ฉันเข้าใจ
nawfal

ส่วนของรหัสนั้นคือ "โปรดใช้เครื่องมือวิเคราะห์รหัส" ฉันไม่คิดว่า @TamasCzinege พูดว่าที่ใดก็ตามที่ใช้Collection<T>จะปกป้องคอลเลกชันพื้นฐานของคุณทันที
chakrit
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.