2016-05-03 53 views
12

dato un ICollection<T> un'istanza esistente (ad esempio dest) qual è il modo più efficiente e leggibile per aggiungere elementi da un IEnumerable<T>?Come posso aggiungere un IEnumerable <T> a un ICollection esistente <T>

Nel mio caso d'uso, ho qualche tipo di metodo di utilità Collect(IEnumerable items) che restituisce una nuova ICollection con gli elementi da items, quindi lo faccio nel modo seguente:

public static ICollection<T> Collect<T>(IEnumerable<T> items) where T:ICollection<T> 
{ 
    ... 
    ICollection<T> dest = Activator.CreateInstance<T>(); 
    items.Aggregate(dest, (acc, item) => { acc.Add(item); return acc; }); 
    ... 
    return dest; 
} 

Domanda: IS c'è un modo "migliore" (più efficiente o leggibile) di farlo?

UPDATE: Penso che l'uso di Aggregate() è abbastanza fluente e non così inefficiente come invocando ToList().ForEach(). Ma non sembra molto leggibile. Poiché nessun altro concorda con l'uso di Aggregate(), vorrei leggere i motivi per NON utilizzare Aggregate() per questo scopo.

+3

Considererei questo abuso del metodo di estensione 'Aggregato'. – Codor

+0

@Codor Condivido i tuoi sentimenti riguardo allo stato condiviso mutabile. Ma non sto ottenendo un'alternativa "più economica". –

+1

Perché non un ciclo semplice per aggiungere gli elementi a 'dest'? – ckruczek

risposta

13

Basta usare Enumerable.Concat:

IEnumerable<YourType> result = dest.Concat(items); 

Se si desidera un List<T> come usare risultato ToList:

List<YourType> result = dest.Concat(items).ToList(); 
// perhaps: 
dest = result; 

Se dest è in realtà già una lista e si desidera modificare esso uso AddRange:

dest.AddRange(items); 

Aggiornamento:

se si deve aggiungere elementi ad un argomento ICollection<T> metodo si potrebbe usare questo estensione:

public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> seq) 
{ 
    List<T> list = collection as List<T>; 
    if (list != null) 
     list.AddRange(seq); 
    else 
    { 
     foreach (T item in seq) 
      collection.Add(item); 
    } 
} 

// ...

public static void Foo<T>(ICollection<T> dest) 
{ 
    IEnumerable<T> items = ... 
    dest.AddRange(items); 
} 
1
items.ToList().ForEach(dest.Add); 

Se non si desidera creare una nuova istanza di raccolta, creare un metodo di estensione.

public static class Extension 
{ 
    public static void AddRange<T>(this ICollection<T> source, IEnumerable<T> items) 
    { 
     if (items == null) 
     { 
      return; 
     } 

     foreach (T item in items) 
     { 
      source.Add(item); 
     } 
    } 
} 

Quindi è possibile modificare il codice come questo:

 ICollection<T> dest = ...; 
     IEnumerable<T> items = ...; 
     dest.AddRange(items); 
+0

Forse 'AddRange'? A proposito, butti via il valore di ritorno. –

+0

@SebastianSchulz 'ToList()' creerà una nuova istanza (temporanea in questo caso) che verrà successivamente scartata da GC. Penso che non sia più efficiente. –

+1

@UweKeim 'AddRange' è solo disponibile su' Elenco' –

0

maggior efficienza:

foreach(T item in itens) dest.Add(item) 

La maggior parte leggibile (MA inefficiente perché sta creando una lista usa e getta):

items.ToList().ForEach(dest.Add); 

meno leggibile, ma non è così inefficiente:

items.Aggregate(dest, (acc, item) => { acc.Add(item); return acc; }); 
+4

That 'ToList.ForEach' è uno degli approcci più imbarazzanti. Stai creando un elenco "usa e getta" \t solo per poter usare 'List.ForEach'. Quindi questo enumererà l'intera collezione per riempire un'altra lista. Quindi quell'elenco verrà enumerato di nuovo per aggiungere gli elementi alla raccolta originale. Mi fa sempre rabbrividire quando vedo questo. È uno dei motivi per cui LINQ ha una cattiva reputazione in alcune aziende. –

+1

Sono completamente d'accordo con te @TimSchmelter. Ecco perché ho incluso nella mia risposta anche la più efficiente. Ma seguirò il tuo avviso e includerò quella nota nella mia risposta. – Gaspium

10

Personalmente mi piacerebbe andare con un commento di @ ckruczek di un ciclo foreach:

foreach (var item in items) 
    dest.Add(item); 

Semplice, pulito, e praticamente tutti capiscono subito cosa fa.

Se si insiste su una chiamata di metodo che nasconde il ciclo, alcune persone definiscono un metodo di estensione personalizzato ForEach per IEnumerable<T>, simile a quello definito per List<T>. L'implementazione è banale:

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) { 
    if (source == null) throw new ArgumentNullException(nameof(source)); 
    if (action == null) throw new ArgumentNullException(nameof(action)); 
    foreach (item in source) 
     action(item); 
} 

Dato che, si sarebbe in grado di scrivere

items.ForEach(dest.Add); 

non vedo molto beneficio a me stesso, ma non svantaggi sia.

+4

Perché non aggiungere semplicemente il metodo di estensione 'AddRange' per' ICollection 'invece di' ForEach'? Questo è molto più pulito e ridurrebbe anche l'overhead operativo. – Leri

+1

@Leri Questa non è una cattiva idea, ma è più complicata. 'AddRange', disponibile per' List ', funziona meglio di una catena di' Add's (quando possibile). Un 'AddRange' personalizzato che potresti aggiungere per 'ICollection ' dovrebbe avere lo stesso effetto (di nuovo quando possibile). Un'implementazione che si traduce incondizionatamente in chiamate ripetute a "ICollection .Add" potrebbe IMO essere fuorviante. – hvd

+6

Post di blog obbligatorio di Eric Lippert: "foreach" vs ForEach ": https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/ –

4

Abbiamo effettivamente scritto un metodo di estensione per questo (insieme a un gruppo di altri metodi di estensione ICollection):

public static class CollectionExt 
{ 
    public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> source) 
    { 
     Contract.Requires(collection != null); 
     Contract.Requires(source != null); 

     foreach (T item in source) 
     { 
      collection.Add(item); 
     } 
    } 
} 

Quindi possiamo semplicemente usare AddRange() su un ICollection():

ICollection<int> test = new List<int>(); 
test.AddRange(new [] {1, 2, 3}); 

Nota: Se si desidera utilizzare List<T>.AddRange() se la raccolta sottostante era di tipo List<T>, è possibile implementare il metodo di estensione in questo modo:

public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> source) 
{ 
    var asList = collection as List<T>; 

    if (asList != null) 
    { 
     asList.AddRange(source); 
    } 
    else 
    { 
     foreach (T item in source) 
     { 
      collection.Add(item); 
     } 
    } 
} 
Problemi correlati