TambahkanRange ke Koleksi

109

Seorang rekan kerja hari ini bertanya kepada saya bagaimana cara menambahkan rentang ke koleksi. Dia memiliki kelas yang diwarisi Collection<T>. Ada properti get-only dari jenis tersebut yang sudah berisi beberapa item. Dia ingin menambahkan item dalam koleksi lain ke koleksi properti. Bagaimana dia bisa melakukannya dengan cara yang ramah C # 3? (Perhatikan batasan tentang properti get-only, yang mencegah solusi seperti melakukan Union dan menetapkan ulang.)

Tentu, kedepan dengan Properti. Tambahkan akan berfungsi. Tapi List<T>AddRange bergaya akan jauh lebih elegan.

Cukup mudah untuk menulis metode ekstensi:

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

Tapi saya merasa saya menemukan kembali kemudi. Saya tidak menemukan sesuatu yang serupa di System.Linqatau morelinq .

Desain yang buruk? Panggil saja Tambahkan? Kehilangan yang sudah jelas?

TrueWill
sumber
5
Ingatlah bahwa Q dari LINQ adalah 'query' dan benar-benar tentang pengambilan data, proyeksi, transformasi, dll. Memodifikasi koleksi yang ada sebenarnya tidak termasuk dalam ranah tujuan yang dimaksudkan LINQ, itulah mengapa LINQ tidak memberikan apa pun- of the box untuk ini. Tetapi metode penyuluhan (dan khususnya sampel Anda) akan ideal untuk ini.
Levi
Satu masalah, ICollection<T>sepertinya tidak punya Addmetode. msdn.microsoft.com/en-us/library/… Namun Collection<T>punya satu.
Tim Goodman
@TimGoodman - Itu adalah antarmuka non-umum. Lihat msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill
"Memodifikasi koleksi yang ada benar-benar tidak termasuk dalam ranah tujuan yang dimaksudkan LINQ". @Levi Lalu mengapa harus Add(T item)di tempat pertama? Sepertinya pendekatan setengah matang untuk menawarkan kemampuan untuk menambahkan satu item dan kemudian mengharapkan semua penelepon untuk mengulang untuk menambahkan lebih dari satu pada satu waktu. Pernyataan Anda memang benar, IEnumerable<T>tetapi saya mendapati diri saya frustrasi ICollectionslebih dari satu kali. Saya tidak setuju dengan Anda, hanya ventilasi.
akousmata

Jawaban:

62

Tidak, ini sepertinya masuk akal. Ada metode List<T>.AddRange () yang pada dasarnya hanya melakukan ini, tetapi mengharuskan koleksi Anda menjadi konkret List<T>.

Reed Copsey
sumber
1
Terima kasih; sangat benar, tetapi sebagian besar properti publik mengikuti pedoman MS dan bukan Daftar.
TrueWill
7
Ya - Saya memberikannya lebih sebagai alasan mengapa saya tidak berpikir ada masalah dengan melakukan ini. Sadarilah bahwa ini akan kurang efisien daripada versi List <T> (karena daftar <T> dapat dialokasikan sebelumnya)
Reed Copsey
Berhati-hatilah karena metode AddRange di .NET Core 2.2 mungkin menunjukkan perilaku aneh jika digunakan secara tidak benar, seperti yang ditunjukkan dalam masalah ini: github.com/dotnet/core/issues/2667
Bruno
36

Coba transmisikan ke List dalam metode ekstensi sebelum menjalankan loop. Dengan cara itu Anda dapat memanfaatkan kinerja 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);
        }
    }
}
rymdsmurf.dll
sumber
2
The asOperator tidak akan pernah membuang. Jika destinationtidak dapat dilemparkan, listakan menjadi nol dan elseblok akan dieksekusi.
rymdsmurf
4
arrgggh! Tukar cabang kondisi, untuk cinta dari semua yang suci!
nicodemus13
13
Sebenarnya saya serius. Alasan utamanya adalah karena beban kognitif ekstra, yang seringkali sangat sulit. Anda terus-menerus mencoba mengevaluasi kondisi negatif, yang biasanya relatif sulit, Anda memiliki kedua cabang, itu (IMO) lebih mudah untuk mengatakan 'jika null' lakukan ini, 'lain' lakukan ini, daripada sebaliknya. Ini juga tentang default, mereka harus menjadi konsep positif sesering mungkin, .eg `if (! Thing.IsDisabled) {} ​​else {} 'mengharuskan Anda untuk berhenti dan berpikir' ah, tidak dinonaktifkan berarti diaktifkan, benar, mendapatkan itu, jadi cabang lainnya adalah ketika IS dinonaktifkan). Sulit diurai.
nicodemus13
13
Menafsirkan "sesuatu! = Null" tidak lebih sulit daripada menafsirkan "sesuatu == null". Namun operator negasi adalah hal yang sama sekali berbeda, dan dalam contoh terakhir Anda menulis ulang pernyataan if-else-akan menghilangkan operator tersebut. Itu adalah perbaikan secara obyektif, tetapi yang tidak terkait dengan pertanyaan awal. Dalam kasus tertentu, kedua bentuk tersebut adalah masalah preferensi pribadi, dan saya lebih suka operator "! =" -, mengingat alasan di atas.
rymdsmurf
15
Pencocokan pola akan membuat semua orang senang ... ;-)if (destination is List<T> list)
Jacob Foshee
28

Karena .NET4.5jika Anda ingin satu baris, Anda dapat menggunakan System.Collections.GenericForEach.

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

atau bahkan lebih pendek seperti

source.ForEach(destination.Add);

Dari segi kinerja, itu sama untuk setiap loop (gula sintaksis).

Juga jangan mencoba menetapkannya seperti

var x = source.ForEach(destination.Add) 

penyebabnya ForEachbatal.

Sunting: Disalin dari komentar, pendapat Lipert di ForEach

Matas Vaitkevicius
sumber
9
Secara pribadi saya dengan Lippert yang satu ini: blogs.msdn.com/b/ericlippert/archive/2009/05/18/...
TrueWill
1
Haruskah itu menjadi source.ForEach (destination.Add)?
Frank
4
ForEachtampaknya hanya didefinisikan pada List<T>, bukan Collection?
Pelindung satu
Lippert sekarang dapat ditemukan di web.archive.org/web/20190316010649/https://…
user7610
Tautan yang diperbarui ke entri blog Eric Lippert: Petualangan Luar Biasa dalam Coding | "Foreach" vs "ForEach"
Alexander
19

Ingatlah bahwa masing-masing Addakan memeriksa kapasitas koleksi dan mengubah ukurannya kapan pun diperlukan (lebih lambat). Dengan AddRangekoleksi akan diatur kapasitasnya dan kemudian ditambahkan item (lebih cepat). Metode ekstensi ini akan sangat lambat, tetapi akan berhasil.

jvitor83
sumber
3
Untuk menambahkan ini, juga akan ada pemberitahuan perubahan koleksi untuk setiap penambahan, sebagai lawan dari satu pemberitahuan massal dengan AddRange.
Nick Udell
3

Ini adalah versi yang lebih maju / siap produksi:

    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;
        }
    }
MovGP0
sumber
Jawaban rymdsmurf mungkin terlihat naif, terlalu sederhana, tetapi berfungsi dengan daftar yang heterogen. Apakah mungkin membuat kode ini mendukung kasus penggunaan ini?
richardsonwtr
Misalnya: destinationadalah daftar Shape, kelas abstrak. sourceadalah daftar dari Circle, kelas yang diwariskan.
richardsonwtr
1

Semua kelas C5 Generic Collections Library mendukung AddRangemetode ini. C5 memiliki antarmuka yang jauh lebih kuat yang benar-benar memperlihatkan semua fitur implementasi yang mendasarinya dan kompatibel dengan antarmuka dengan antarmuka System.Collections.Generic ICollectiondan IList, yang berarti bahwa C5koleksi dapat dengan mudah diganti sebagai implementasi yang mendasarinya.

Marcus Griep
sumber
0

Anda dapat menambahkan rentang IEnumerable Anda ke daftar kemudian mengatur ICollection = ke daftar.

        IEnumerable<T> source;

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

        ICollection<item> destination = list;
Jonathan Jansen
sumber
3
Sementara ini berfungsi secara fungsional, itu melanggar pedoman Microsoft untuk membuat properti koleksi hanya baca ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell
0

Atau Anda bisa membuat ekstensi ICollection seperti ini:

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

        return @this;
    }

Menggunakannya akan seperti menggunakannya dalam daftar:

collectionA.AddRange(IEnumerable<object> items);
Katarina Kelam
sumber