AggiungiRange a una raccolta


109

Un collega mi ha chiesto oggi come aggiungere un intervallo a una raccolta. Ha una classe che eredita da Collection<T>. Esiste una proprietà di sola ricezione di quel tipo che contiene già alcuni elementi. Vuole aggiungere gli elementi in un'altra raccolta alla raccolta di proprietà. Come può farlo in modo compatibile con C # 3? (Notare il vincolo sulla proprietà get-only, che impedisce soluzioni come l'unione e la riassegnazione.)

Certo, un foreach con Property. Aggiungi funzionerà. Ma un List<T>AddRange in stile sarebbe molto più elegante.

È abbastanza facile scrivere un metodo di estensione:

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

Ma ho la sensazione di reinventare la ruota. Non ho trovato nulla di simile in System.Linqo morelinq .

Cattivo design? Basta chiamare Aggiungi? Manca l'ovvio?


5
Ricorda che la Q di LINQ è "query" e riguarda il recupero, la proiezione, la trasformazione dei dati, ecc. La modifica di raccolte esistenti non rientra davvero nel campo dello scopo previsto di LINQ, motivo per cui LINQ non fornisce nulla. di-the-box per questo. Ma i metodi di estensione (e in particolare il tuo campione) sarebbero ideali per questo.
Levi

Un problema, ICollection<T>non sembra avere un Addmetodo. msdn.microsoft.com/en-us/library/… Tuttavia ne Collection<T>ha uno.
Tim Goodman

@TimGoodman - Questa è l'interfaccia non generica. Vedere msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill

"La modifica delle raccolte esistenti non rientra davvero nel regno dello scopo previsto di LINQ". @Levi Allora perché anche Add(T item)in primo luogo? Sembra un approccio incompleto per offrire la possibilità di aggiungere un singolo elemento e quindi aspettarsi che tutti i chiamanti iterino per aggiungerne più di uno alla volta. La tua affermazione è certamente vera IEnumerable<T>ma mi sono trovato frustrato ICollectionsin più di un'occasione. Non sono in disaccordo con te, mi sto solo sfogando.
akousmata

Risposte:


62

No, questo sembra perfettamente ragionevole. Esiste un metodo List<T>.AddRange () che fondamentalmente fa proprio questo, ma richiede che la tua raccolta sia concreta List<T>.


1
Grazie; molto vero, ma la maggior parte delle proprietà pubbliche segue le linee guida MS e non sono elenchi.
TrueWill

7
Sì, lo stavo dando più come motivazione per il motivo per cui non penso che ci sia un problema nel farlo. Renditi conto che sarà meno efficiente della versione List <T> (poiché la lista <T> può pre-allocare)
Reed Copsey,

Basta fare attenzione che il metodo AddRange in .NET Core 2.2 potrebbe mostrare un comportamento strano se usato in modo errato, come mostrato in questo numero: github.com/dotnet/core/issues/2667
Bruno

36

Prova a trasmettere a List nel metodo di estensione prima di eseguire il ciclo. In questo modo puoi sfruttare le prestazioni di 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
L' asoperatore non lancerà mai. Se destinationnon può essere lanciato, listsarà nullo e il elseblocco verrà eseguito.
rymdsmurf

4
arrgggh! Scambia i rami della condizione, per l'amore di tutto ciò che è santo!
nicodemus13

13
Dico sul serio, in realtà, il motivo principale è che è un carico cognitivo extra, che spesso è molto difficile. Stai costantemente cercando di valutare le condizioni negative, che di solito è relativamente difficile, hai comunque entrambi i rami, è (IMO) più facile dire "se nullo" fai questo, "altrimenti" fallo, piuttosto che il contrario. Riguarda anche i valori predefiniti, dovrebbero essere il concetto positivo il più spesso possibile, .eg `if (! Thing.IsDisabled) {} ​​else {} 'richiede che ti fermi e pensi' ah, non è disabilitato significa che è abilitato, giusto, capito, quindi l'altro ramo è quando è disabilitato). Difficile da analizzare.
nicodemus13

13
Interpretare "qualcosa! = Null" non è più difficile che interpretare "qualcosa == null". L'operatore di negazione tuttavia è una cosa completamente diversa e nel tuo ultimo esempio la riscrittura dell'istruzione if-else eliminerebbe quell'operatore . Questo è oggettivamente un miglioramento, ma non è correlato alla domanda originale. In quel caso particolare le due forme sono una questione di preferenze personali, e preferirei l'operatore "! =" -, dato il ragionamento sopra.
rymdsmurf

15
Il pattern matching renderà tutti felici ... ;-)if (destination is List<T> list)
Jacob Foshee

28

Dal momento che .NET4.5se vuoi una riga puoi usare System.Collections.GenericForEach.

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

o anche più breve come

source.ForEach(destination.Add);

Dal punto di vista delle prestazioni è lo stesso di ogni ciclo (zucchero sintattico).

Inoltre , non provare ad assegnarlo come

var x = source.ForEach(destination.Add) 

la causa ForEachè nulla.

Modifica: copiato dai commenti, l'opinione di Lipert su ForEach


9
Personalmente sono con Lippert su questo: blogs.msdn.com/b/ericlippert/archive/2009/05/18/…
TrueWill

1
Dovrebbe essere source.ForEach (destination.Add)?
Frank

4
ForEachsembra essere definito solo su List<T>, no Collection?
Protector one

Lippert può ora essere trovato su web.archive.org/web/20190316010649/https://…
user7610

Link aggiornato al post del blog di Eric Lippert: Fabulous Adventures in Coding | "Foreach" vs "ForEach"
Alexander

19

Ricorda che ognuno Addcontrollerà la capacità della raccolta e la ridimensionerà ogni volta che sarà necessario (più lentamente). Con AddRangela raccolta verrà impostata la capacità e quindi aggiunti gli articoli (più velocemente). Questo metodo di estensione sarà estremamente lento, ma funzionerà.


3
Per aggiungere a questo, ci sarà anche una notifica di modifica della raccolta per ogni aggiunta, invece di una notifica in blocco con AddRange.
Nick Udell

3

Ecco una versione un po 'più avanzata / pronta per la produzione:

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

La risposta di rymdsmurf può sembrare ingenua, troppo semplice, ma funziona con elenchi eterogenei. È possibile fare in modo che questo codice supporti questo caso d'uso?
richardsonwtr

Ad esempio: destinationè un elenco di Shape, una classe astratta. sourceè un elenco di Circleuna classe ereditata.
richardsonwtr

1

Le classi della libreria di raccolte generiche C5 supportano tutte il AddRangemetodo. C5 ha un'interfaccia molto più robusta che espone effettivamente tutte le caratteristiche delle sue implementazioni sottostanti ed è compatibile con le interfacce System.Collections.Generic ICollectione IList, il che significa che C5le raccolte di possono essere facilmente sostituite come implementazione sottostante.


0

Puoi aggiungere il tuo intervallo IEnumerable a un elenco, quindi impostare ICollection = nell'elenco.

        IEnumerable<T> source;

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

        ICollection<item> destination = list;

3
Sebbene funzioni funzionalmente, infrange le linee guida di Microsoft per rendere le proprietà della raccolta di sola lettura ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell

0

Oppure puoi semplicemente creare un'estensione ICollection come questa:

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

        return @this;
    }

Usarlo sarebbe proprio come usarlo in un elenco:

collectionA.AddRange(IEnumerable<object> items);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.