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?

TrueWill
źródło
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>.

Reed Copsey
źródło
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);
        }
    }
}
rymdsmurf
źródło
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

Matas Vaitkevicius
źródło
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.

jvitor83
źródło
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;
        }
    }
MovGP0
źródło
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ę.

Marcus Griep
źródło
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;
Jonathan Jansen
źródło
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);
Katarina Kelam
źródło