AddRange до колекції


109

Сьогодні колега запитав мене, як додати асортимент до колекції. У нього є клас, який успадковує від Collection<T>. Існує властивість лише цього типу, яка вже містить деякі елементи. Він хоче додати предмети в іншій колекції до колекції майна. Як він може це зробити на C-3? (Зверніть увагу на обмеження щодо властивості get-only, яке перешкоджає таким рішенням, як створення Union і перепризначення.)

Звичайно, передбачення з майном. Додати буде працювати. Але List<T>-style 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 не забезпечує нічого для цього. Але для цього ідеально підійдуть методи розширення (і, зокрема, ваш зразок).
Леві

Одна проблема, ICollection<T>схоже, не має Addметоду. msdn.microsoft.com/en-us/library/… Однак Collection<T>є одна.
Тім Гудман

@TimGoodman - це негенеричний інтерфейс. Дивіться msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill

"Модифікація існуючих колекцій дійсно не входить у сферу цільового призначення LINQ". @Levi Тоді чому взагалі Add(T item)на першому місці? Здається, що напівфабрикат підходу пропонує можливість додати один елемент, а потім очікує, що всі абоненти пройдуть ітерацію, щоб додати більше одного за один раз. Твоє твердження, безумовно, вірно, IEnumerable<T>але я не ICollectionsраз стикався. Я з вами не згоден, просто вентилюю.
akousmata

Відповіді:


62

Ні, це здається цілком розумним. Існує метод List<T>.AddRange (), який в основному робить саме це, але вимагає, щоб ваша колекція була конкретною List<T>.


1
Дякую; дуже вірно, але більшість публічних властивостей відповідають рекомендаціям MS та не є Списками.
TrueWill

7
Так - я давав це більше як обґрунтування того, чому я не думаю, що в цьому є проблеми. Просто зрозумійте, що вона буде менш ефективною, ніж версія <T> версії (оскільки список <T> можна попередньо виділити)
Reed Copsey,

Просто подбайте про те, щоб метод AddRange в .NET Core 2.2 міг проявити дивну поведінку при неправильному використанні, як показано в цьому випуску: github.com/dotnet/core/isissue/2667
Бруно

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
arrgggh! Поміняйте гілками умови, на любов до всього, що святе!
nicodemus13

13
Насправді я серйозно серйозна. Основна причина полягає в тому, що це додаткове когнітивне навантаження, що часто буває дуже складно. Ви постійно намагаєтеся оцінити негативні умови, що зазвичай відносно важко, у вас все одно є обидві гілки, простіше сказати (ІМО) - "якщо нуль", зроби це "ще", а не навпаки. Йдеться також про значення за замовчуванням, вони повинні бути позитивною концепцією якомога частіше, .eg `якщо (! Thing.IsDisabled) {} ​​else {} 'вимагає, щоб ви зупинилися і подумали" ах, не вимкнено, значить ввімкнено, так, отримав це, тому інша гілка є, коли вона відключена). Важко розібратися.
nicodemus13

13
Інтерпретувати "щось! = Null" не складніше, ніж інтерпретувати "щось == null". Однак оператор заперечення - це зовсім інша річ, і у вашому останньому прикладі переписування заяви-if-else усуне цього оператора. Це об'єктивно є вдосконаленням, але воно не пов'язане з початковим питанням. У цьому конкретному випадку обидві форми - це питання особистих уподобань, і я вважаю за краще оператор "! =", Враховуючи вищезазначені міркування.
rymdsmurf

15
if (destination is List<T> list)
Узгодження

28

Оскільки, .NET4.5якщо ви хочете однолінійний, ви можете використовувати System.Collections.GenericForEach.

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

або навіть коротше як

source.ForEach(destination.Add);

Ефективність роботи така ж, як і для кожного циклу (синтаксичний цукор).

Також не намагайтеся призначити його так

var x = source.ForEach(destination.Add) 

причина ForEachнедійсна.

Редагувати: Скопійовано з коментарів, думку Ліперта щодо ForEach


9
Особисто я з Ліппертом на цьому: blogs.msdn.com/b/ericlippert/archive/2009/05/18/…
TrueWill

1
Чи повинно бути джерело.ForEach (призначення.Add)?
Френк

4
ForEachздається, визначено лише на List<T>, чи не так Collection?
Захисник один

Тепер Lippert можна знайти на веб-
user7610

Оновлене посилання на повідомлення в блозі Еріка Ліпперта: Казкові пригоди в кодуванні | "Передбачити" проти "ForEach"
Олександр

19

Пам’ятайте, що кожен Addперевірятиме місткість колекції та змінює її розмір, коли це необхідно (повільніше). З AddRange, колекція буде встановлена ​​ємність, а потім додаються елементи (швидше). Цей метод розширення буде надзвичайно повільним, але буде працювати.


3
Щоб додати до цього, для кожного додавання також буде повідомлення про зміну колекції, на відміну від одного масового повідомлення з AddRange.
Нік Уделл

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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.