เพิ่มช่วงไปยังคอลเลกชัน


109

เพื่อนร่วมงานถามฉันในวันนี้ว่าจะเพิ่มช่วงลงในคอลเล็กชันได้อย่างไร Collection<T>เขามีชั้นเรียนที่สืบทอดจาก มีคุณสมบัติรับอย่างเดียวประเภทนั้นที่มีบางรายการอยู่แล้ว เขาต้องการเพิ่มรายการในคอลเล็กชันอื่นในคอลเล็กชันคุณสมบัติ เขาทำแบบ C # 3-friendly ได้อย่างไร? (สังเกตข้อ จำกัด เกี่ยวกับคุณสมบัติ get-only ซึ่งป้องกันโซลูชันเช่นการทำ Union และการกำหนดใหม่)

แน่นอน foreach กับ Property แอดจะทำงาน แต่List<T>AddRange สไตล์จะดูหรูหรากว่ามาก

ง่ายพอที่จะเขียนวิธีการขยาย:

public static class CollectionHelpers
{
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

แต่ฉันมีความรู้สึกว่าฉันกำลังคิดค้นล้อใหม่ ฉันไม่พบอะไรในที่คล้ายกันSystem.Linqหรือmorelinq

ออกแบบไม่ดี? แค่โทรเพิ่ม? หายไปอย่างชัดเจน?


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

ปัญหาหนึ่งICollection<T>ดูเหมือนจะไม่มีAddวิธีการ msdn.microsoft.com/en-us/library/… อย่างไรก็ตามCollection<T>มีหนึ่ง
Tim Goodman

@TimGoodman - นั่นคืออินเทอร์เฟซที่ไม่ใช่แบบทั่วไป ดูmsdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill

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

คำตอบ:


62

ไม่นี่ดูสมเหตุสมผลดี มีความเป็นList<T>.AddRange ()วิธีการที่พื้นไม่เพียงแค่นี้ List<T>แต่ต้องมีคอลเลกชันของคุณให้เป็นรูปธรรม


1
ขอบคุณ; จริงมาก แต่ทรัพย์สินสาธารณะส่วนใหญ่เป็นไปตามหลักเกณฑ์ของ MS และไม่ใช่รายการ
TrueWill

7
ใช่ - ฉันให้เหตุผลมากขึ้นว่าทำไมฉันไม่คิดว่าจะมีปัญหากับการทำเช่นนี้ เพิ่งทราบว่าจะมีประสิทธิภาพน้อยกว่ารุ่น List <T> (เนื่องจากรายการ <T> สามารถจัดสรรล่วงหน้าได้)
Reed Copsey

โปรดดูแลว่าวิธีการ AddRange ใน. NET Core 2.2 อาจแสดงพฤติกรรมแปลก ๆ หากใช้ไม่ถูกต้องดังที่แสดงในฉบับนี้: github.com/dotnet/core/issues/2667
Bruno

36

ลองแคสต์ไปยังรายการในวิธีการขยายก่อนที่จะเรียกใช้ลูป ด้วยวิธีนี้คุณสามารถใช้ประโยชน์จากประสิทธิภาพของ List.AddRange

public static void AddRange<T>(this ICollection<T> destination,
                               IEnumerable<T> source)
{
    List<T> list = destination as List<T>;

    if (list != null)
    {
        list.AddRange(source);
    }
    else
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

2
asผู้ประกอบการจะไม่โยน หากdestinationไม่สามารถแคสต์ได้listจะเป็นโมฆะและelseบล็อกจะดำเนินการ
rymdsmurf

4
อร๊ากกก! เปลี่ยนกิ่งไม้เงื่อนไขเพื่อความรักของทุกสิ่งที่ศักดิ์สิทธิ์!
nicodemus13

13
ฉันเป็นคนจริงจังจริงๆเหตุผลหลักคือมันมีภาระทางความคิดเพิ่มขึ้นซึ่งมักจะค่อนข้างยาก คุณพยายามประเมินเงื่อนไขเชิงลบอยู่ตลอดเวลาซึ่งโดยปกติจะค่อนข้างยากคุณมีทั้งสองสาขาอยู่แล้วมัน (IMO) ง่ายกว่าที่จะพูดว่า 'ถ้าว่าง' ทำสิ่งนี้ 'อื่น' ทำสิ่งนี้แทนที่จะเป็นตรงกันข้าม นอกจากนี้ยังเกี่ยวกับค่าเริ่มต้นควรเป็นแนวคิดเชิงบวกให้บ่อยที่สุดเท่าที่จะเป็นไปได้, .eg `` if (! thing.IsDisabled) {} ​​else {} 'ต้องการให้คุณหยุดและคิดว่า' อ่าไม่ได้ปิดใช้งานหมายถึงเปิดใช้งานใช่ไหม ได้รับสิ่งนั้นดังนั้นสาขาอื่นคือเมื่อปิดใช้งาน IS) ยากที่จะแยกวิเคราะห์
nicodemus13

13
การตีความ "something! = null" ไม่ยากไปกว่าการตีความ "something == null" ผู้ประกอบการปฏิเสธ แต่เป็นสิ่งที่แตกต่างอย่างสิ้นเชิงและในตัวอย่างล่าสุดของคุณเขียนใหม่ถ้า-อื่นคำสั่งจะelliminateผู้ประกอบการที่ นั่นเป็นการปรับปรุงอย่างเป็นกลาง แต่เป็นสิ่งที่ไม่เกี่ยวข้องกับคำถามเดิม ในกรณีนี้ทั้งสองรูปแบบเป็นเรื่องของความชอบส่วนบุคคลและฉันต้องการตัวดำเนินการ "! =" - ตามเหตุผลข้างต้น
rymdsmurf

15
การจับคู่รูปแบบจะทำให้ทุกคนมีความสุข ... ;-)if (destination is List<T> list)
Jacob Foshee

28

เนื่องจาก.NET4.5หากคุณต้องการซับเดียวคุณสามารถใช้System.Collections.GenericForEach ได้

source.ForEach(o => destination.Add(o));

หรือสั้นกว่าด้วย

source.ForEach(destination.Add);

ประสิทธิภาพที่ชาญฉลาดมันเหมือนกับสำหรับแต่ละลูป (น้ำตาลวากยสัมพันธ์)

นอกจากนี้ยังไม่ได้พยายามที่กำหนดเช่น

var x = source.ForEach(destination.Add) 

สาเหตุForEachถือเป็นโมฆะ

แก้ไข:คัดลอกจากความคิดเห็นความเห็นของ Lipert เกี่ยวกับ ForEach


9
ส่วนตัวฉันกับ Lippert บนนี้: blogs.msdn.com/b/ericlippert/archive/2009/05/18/...
TrueWill

1
ควรจะเป็น sourceForEach (destination.Add) หรือไม่?
Frank

4
ForEachดูเหมือนจะถูกกำหนดไว้List<T>เท่านั้นไม่ใช่Collectionหรือ?
ผู้พิทักษ์

Lippert ได้แล้วที่web.archive.org/web/20190316010649/https://…
user7610

อัปเดตลิงก์ไปยังบล็อกโพสต์ของ Eric Lippert: การผจญภัยที่ยอดเยี่ยมในการเข้ารหัส | “ foreach” กับ“ ForEach”
Alexander

19

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


3
ในการเพิ่มสิ่งนี้จะมีการแจ้งเตือนการเปลี่ยนแปลงคอลเล็กชันสำหรับการเพิ่มแต่ละครั้งซึ่งต่างจากการแจ้งเตือนจำนวนมากด้วย AddRange
Nick Udell

3

นี่คือเวอร์ชันขั้นสูง / พร้อมใช้งานจริงอีกเล็กน้อย:

    public static class CollectionExtensions
    {
        public static TCol AddRange<TCol, TItem>(this TCol destination, IEnumerable<TItem> source)
            where TCol : ICollection<TItem>
        {
            if(destination == null) throw new ArgumentNullException(nameof(destination));
            if(source == null) throw new ArgumentNullException(nameof(source));

            // don't cast to IList to prevent recursion
            if (destination is List<TItem> list)
            {
                list.AddRange(source);
                return destination;
            }

            foreach (var item in source)
            {
                destination.Add(item);
            }

            return destination;
        }
    }

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

เช่น: destinationเป็นรายการShapeคลาสนามธรรม sourceคือรายชื่อCircleคลาสที่สืบทอดมา
richardsonwtr

1

C5 ทั่วไปคอลเลกชันห้องสมุดชั้นเรียนทั้งหมดสนับสนุนAddRangeวิธีการ C5 มีอินเทอร์เฟซที่แข็งแกร่งกว่ามากซึ่งจะเปิดเผยคุณสมบัติทั้งหมดของการใช้งานพื้นฐานและเข้ากันได้กับอินเทอร์เฟซSystem.Collections.Generic ICollectionและIListอินเทอร์เฟซซึ่งหมายความว่าC5คอลเลกชันของสามารถทดแทนได้อย่างง่ายดายเป็นการนำไปใช้งาน


0

คุณสามารถเพิ่มช่วง IEnumerable ลงในรายการจากนั้นตั้งค่า ICollection = ในรายการ

        IEnumerable<T> source;

        List<item> list = new List<item>();
        list.AddRange(source);

        ICollection<item> destination = list;

3
แม้ว่าสิ่งนี้จะใช้งานได้จริง แต่ก็ทำลายแนวทางของ Microsoft เพื่อให้คุณสมบัติคอลเลกชันเป็นแบบอ่านอย่างเดียว ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell

0

หรือคุณสามารถสร้างส่วนขยาย ICollection ได้ดังนี้:

 public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
    {
        foreach(var item in items)
        {
            @this.Add(item);
        }

        return @this;
    }

การใช้มันจะเหมือนกับการใช้ในรายการ:

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