AddRange to a Collection


109

Współpracownik zapytał mnie dzisiaj, jak dodać zakres do kolekcji. Ma klasę, która dziedziczy Collection<T>. Istnieje właściwość get-only tego typu, która zawiera już pewne elementy. Chce dodać elementy z innej kolekcji do kolekcji właściwości. Jak może to zrobić w sposób przyjazny dla C # 3? (Zwróć uwagę na ograniczenie dotyczące właściwości get-only, które zapobiega rozwiązaniom takim jak wykonywanie Union i ponowne przypisywanie).

Jasne, foreach z Property. Dodaj zadziała. Ale List<T>AddRange w stylu a byłby znacznie bardziej elegancki.

Łatwo jest napisać metodę rozszerzającą:

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

Ale mam wrażenie, że wymyślam koło na nowo. Nie znalazłem nic podobnego w System.Linqlub morelinq .

Zły projekt? Po prostu zadzwoń Dodaj? Brakuje oczywistości?


5
Pamiętaj, że Q z LINQ to „zapytanie” i tak naprawdę dotyczy pobierania danych, projekcji, transformacji itp. Modyfikowanie istniejących kolekcji naprawdę nie wchodzi w zakres zamierzonego celu LINQ, dlatego LINQ nie zapewnia niczego poza- z pudełka do tego. Ale metody rozszerzające (a zwłaszcza twoja próbka) byłyby do tego idealne.
Levi

ICollection<T>Wydaje się, że jeden problem nie ma Addmetody. msdn.microsoft.com/en-us/library/… Jednak Collection<T>ma jeden.
Tim Goodman,

@TimGoodman - To nieogólny interfejs. Zobacz msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill,

„Modyfikowanie istniejących kolekcji naprawdę nie mieści się w sferze zamierzonego celu LINQ”. @Levi W takim razie po co Add(T item)w ogóle mieć ? Wydaje się, że jest to niedopracowane podejście do oferowania możliwości dodania pojedynczego elementu, a następnie oczekiwania, że ​​wszyscy dzwoniący będą iterować, aby dodać więcej niż jeden naraz. Twoje stwierdzenie z pewnością jest prawdziwe, IEnumerable<T>ale nieraz byłem sfrustrowany ICollections. Nie zgadzam się z tobą, po prostu wyładowuję.
akousmata

Odpowiedzi:


62

Nie, wydaje się to całkiem rozsądne. Istnieje metoda List<T>.AddRange () , która w zasadzie właśnie to robi, ale wymaga, aby Twoja kolekcja była konkretna List<T>.


1
Dzięki; bardzo prawdziwe, ale większość dóbr publicznych postępuje zgodnie z wytycznymi państw członkowskich i nie jest listami.
TrueWill

7
Tak - podawałem to bardziej jako uzasadnienie, dlaczego nie sądzę, że jest z tym problem. Po prostu uświadom sobie, że będzie mniej wydajna niż wersja List <T> (ponieważ lista <T> może zostać wstępnie przydzielona)
Reed Copsey

Po prostu uważaj, aby metoda AddRange w .NET Core 2.2 mogła wykazywać dziwne zachowanie, jeśli zostanie użyta nieprawidłowo, jak pokazano w tym numerze: github.com/dotnet/core/issues/2667
Bruno

36

Spróbuj rzutować na List w metodzie rozszerzenia przed uruchomieniem pętli. W ten sposób możesz wykorzystać wydajność 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
asOperator nigdy nie rzuci. Jeśli destinationnie można rzutować, listbędzie miał wartość null i elseblok zostanie wykonany.
rymdsmurf

4
arrgggh! Zamień gałęzie stanu, z miłości do wszystkiego, co święte!
nicodemus13

13
Mówię poważnie, a głównym powodem jest to, że jest to dodatkowe obciążenie poznawcze, które często jest bardzo trudne. Ciągle próbujesz ocenić negatywne warunki, co zwykle jest stosunkowo trudne, i tak masz obie gałęzie, łatwiej jest (IMO) powiedzieć „jeśli null” zrób to, „inaczej” zrób to, a nie odwrotnie. Chodzi także o wartości domyślne, powinny one być pozytywną koncepcją tak często, jak to możliwe, .eg `if (! Rzecz.IsDisabled) {} ​​else {} 'wymaga zatrzymania się i pomyślenia' ah, not is disabled oznacza, że ​​jest włączone, prawda, dostałem to, więc druga gałąź jest wyłączona). Trudne do przeanalizowania.
nicodemus 13

13
Interpretacja „coś! = Null” nie jest trudniejsza niż interpretacja „coś == null”. Jednak operator negacji to zupełnie inna sprawa, a w ostatnim przykładzie przepisywanie if-else-oświadczenie byłoby elliminate tego operatora. Jest to obiektywnie poprawa, ale taka, która nie jest związana z pierwotnym pytaniem. W tym konkretnym przypadku te dwie formy są kwestią osobistych preferencji i wolałbym operator „! =” -, biorąc pod uwagę powyższe rozumowanie.
rymdsmurf

15
Dopasowywanie wzorów uszczęśliwi każdego ... ;-)if (destination is List<T> list)
Jacob Foshee

28

Ponieważ .NET4.5jeśli chcesz mieć jedną linijkę, możesz użyć System.Collections.GenericForEach.

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

lub nawet krócej jak

source.ForEach(destination.Add);

Pod względem wydajności jest taki sam jak dla każdej pętli (cukier syntaktyczny).

Również nie spróbować go jak przypisywanie

var x = source.ForEach(destination.Add) 

przyczyna ForEachjest nieważna.

Edycja: skopiowane z komentarzy, opinia Liperta na temat ForEach


9
Osobiście jestem z Lippertem
TrueWill

1
Czy powinno to być source.ForEach (destination.Add)?
Frank,

4
ForEachwydaje się być zdefiniowany tylko na List<T>, nie Collection?
Protector One

Lippert można teraz znaleźć na web.archive.org/web/20190316010649/https://...
user7610

Zaktualizowany link do wpisu na blogu Erica Lipperta: Fabulous Adventures in Coding | „Foreach” vs „ForEach”
Alexander,

19

Pamiętaj, że każdy Addsprawdzi pojemność kolekcji i zmieni jej rozmiar w razie potrzeby (wolniej). Za AddRangepomocą kolekcji zostanie ustawiona pojemność, a następnie dodane elementy (szybciej). Ta metoda rozszerzenia będzie bardzo powolna, ale zadziała.


3
Aby to dodać, pojawi się również powiadomienie o zmianie kolekcji dla każdego dodania, w przeciwieństwie do jednego powiadomienia zbiorczego z AddRange.
Nick Udell

3

Oto nieco bardziej zaawansowana / gotowa do produkcji wersja:

    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;
        }
    }

Odpowiedź rymdsmurfa może wydawać się naiwna, zbyt prosta, ale działa z niejednorodnymi listami. Czy jest możliwe, aby ten kod obsługiwał ten przypadek użycia?
richardsonwtr

Np .: destinationjest to lista Shape, klasa abstrakcyjna. sourcejest listą Circledziedziczonych klas.
richardsonwtr

1

Wszystkie klasy C5 Generic Collections Library obsługują tę AddRangemetodę. C5 ma znacznie bardziej niezawodny interfejs, który w rzeczywistości ujawnia wszystkie funkcje swoich podstawowych implementacji i jest zgodny z interfejsami System.Collections.Generic ICollectioni IList, co oznacza, że ​​te C5kolekcje można łatwo zastąpić jako podstawową implementację.


0

Możesz dodać swój zakres IEnumerable do listy, a następnie ustawić ICollection = na listę.

        IEnumerable<T> source;

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

        ICollection<item> destination = list;

3
Chociaż to działa funkcjonalnie, łamie wytyczne firmy Microsoft, aby właściwości kolekcji były tylko do odczytu ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell

0

Lub możesz po prostu utworzyć takie rozszerzenie ICollection:

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

        return @this;
    }

Używanie go byłoby jak używanie go na liście:

collectionA.AddRange(IEnumerable<object> items);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.