Come posso aggiungere un elemento a una raccolta IEnumerable <T>?


405

La mia domanda come titolo sopra. Per esempio,

IEnumerable<T> items = new T[]{new T("msg")};
items.ToList().Add(new T("msg2"));

ma dopotutto contiene solo 1 oggetto.

Possiamo avere un metodo simile items.Add(item)?

come il List<T>


4
IEnumerable<T>è pensato solo per l'interrogazione di raccolte. È la spina dorsale per il framework LINQ. E 'sempre un'astrazione di qualche altra collezione, come Collection<T>, List<T>o Array. L'interfaccia fornisce solo un GetEnumeratormetodo che restituisce un'istanza IEnumerator<T>che consente di camminare un elemento alla volta della raccolta estesa. I metodi di estensione LINQ sono limitati da questa interfaccia. IEnumerable<T>è progettato per essere letto solo perché può rappresentare un'aggregazione di parti di più raccolte.
Giordania,

3
Per questo esempio, basta dichiarare IList<T>invece di IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Risposte:


480

Non è possibile, poiché IEnumerable<T>non rappresenta necessariamente una raccolta a cui è possibile aggiungere elementi. In realtà, non rappresenta necessariamente una collezione! Per esempio:

IEnumerable<string> ReadLines()
{
     string s;
     do
     {
          s = Console.ReadLine();
          yield return s;
     } while (!string.IsNullOrEmpty(s));
}

IEnumerable<string> lines = ReadLines();
lines.Add("foo") // so what is this supposed to do??

Quello che puoi fare, tuttavia, è creare un nuovo IEnumerable oggetto (di tipo non specificato) che, quando elencato, fornirà tutti gli elementi di quello vecchio, oltre a alcuni dei tuoi. Usi Enumerable.Concatper quello:

 items = items.Concat(new[] { "foo" });

Ciò non modificherà l'oggetto array (non è possibile inserire comunque elementi negli array). Ma creerà un nuovo oggetto che elencherà tutti gli elementi dell'array e quindi "Foo". Inoltre, quel nuovo oggetto terrà traccia delle modifiche nell'array (cioè ogni volta che lo enumererai, vedrai i valori correnti degli elementi).


3
La creazione di un array per aggiungere un singolo valore è un po 'elevata nell'overhead. È un po 'più riutilizzabile definire un metodo di estensione per un singolo valore Concat.
JaredPar,

4
Dipende - può valerne la pena, ma sono tentato di chiamarlo ottimizzazione prematura a meno che non Concatvenga chiamato ripetutamente in un ciclo; e se lo è, hai comunque problemi più grandi, perché ogni volta Concatottieni un ulteriore livello di enumeratore - quindi alla fine di esso la singola chiamata MoveNextsi tradurrà in una catena di chiamate uguale in lunghezza al numero di iterazioni.
Pavel Minaev,

2
@Pavel, eh. Di solito non mi preoccupa il sovraccarico di memoria della creazione dell'array. È la digitazione extra che trovo fastidiosa :).
JaredPar,

2
Quindi capisco un po 'di più cosa fa IEnumerable. Dovrebbe essere vista solo non intende essere modificata. Grazie ragazzi
ldsenow

7
Ho avuto una richiesta di funzionalità Connect per lo zucchero sintattico per IEnumerable<T>in C #, incluso il sovraccarico operator+come Concat(a proposito, lo sai che in VB, puoi indicizzare IEnumerablecome se fosse un array - utilizza ElementAtsotto il cofano): connect.microsoft.com / VisualStudio / feedback /… . Se avessimo anche due ulteriori sovraccarichi Concatper prendere un singolo oggetto sia a sinistra che a destra, questo ridurrebbe la digitazione a xs +=x - quindi vai a colpire Mads (Torgersen) per dare la priorità a questo in C # 5.0 :)
Pavel Minaev

98

Il tipo IEnumerable<T>non supporta tali operazioni. Lo scopo IEnumerable<T>dell'interfaccia è consentire a un consumatore di visualizzare il contenuto di una raccolta. Non modificare i valori.

Quando esegui operazioni come .ToList (). Aggiungi () stai creando un nuovo List<T>e aggiungendo un valore a quell'elenco . Non ha alcun collegamento con l'elenco originale.

Quello che puoi fare è usare il metodo di estensione Aggiungi per crearne uno nuovo IEnumerable<T>con il valore aggiunto.

items = items.Add("msg2");

Anche in questo caso non modificherà l' IEnumerable<T>oggetto originale . Questo può essere verificato tenendo un riferimento ad esso. Per esempio

var items = new string[]{"foo"};
var temp = items;
items = items.Add("bar");

Dopo questo insieme di operazioni, la variabile temp farà ancora riferimento a un enumerabile con un singolo elemento "foo" nell'insieme di valori mentre gli elementi faranno riferimento a un diverso enumerabile con valori "foo" e "bar".

MODIFICARE

Dimentico costantemente che Aggiungi non è un tipico metodo di estensione IEnumerable<T>perché è uno dei primi che finisco per definire. Ecco qui

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}

12
Si chiama Concat:)
Pavel Minaev il

3
@Pavel, grazie per averlo sottolineato. Anche Concat non funzionerà perché richiede un altro IEnumerable <T>. Ho incollato il tipico metodo di estensione che definisco nei miei progetti.
JaredPar,

10
Non lo chiamerei Addperò, perché Addpraticamente su qualsiasi altro tipo di .NET (non solo sulle raccolte) muta la raccolta sul posto. Forse With? O potrebbe anche essere solo un altro sovraccarico di Concat.
Pavel Minaev,

4
Sono d'accordo che il Concatsovraccarico sarebbe probabilmente una scelta migliore poiché di Addsolito implica la mutabilità.
Dan Abramov,

15
Che dire di modificare il corpo in return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker,

58

Hai preso in considerazione l'utilizzo ICollection<T>o le IList<T>interfacce invece, esistono proprio per il motivo per cui vuoi avere un Addmetodo su un IEnumerable<T>.

IEnumerable<T>viene utilizzato per "contrassegnare" un tipo come ... beh, enumerabile o solo una sequenza di elementi senza necessariamente fornire garanzie sul fatto che l'oggetto reale sottostante supporti l'aggiunta / rimozione di elementi. Ricorda inoltre che queste interfacce implementano in IEnumerable<T>modo da ottenere anche tutti i metodi di estensione che ottieni IEnumerable<T>.


31

Un paio di metodi di estensione brevi e dolci IEnumerablee IEnumerable<T>fallo per me:

public static IEnumerable Append(this IEnumerable first, params object[] second)
{
    return first.OfType<object>().Concat(second);
}
public static IEnumerable<T> Append<T>(this IEnumerable<T> first, params T[] second)
{
    return first.Concat(second);
}   
public static IEnumerable Prepend(this IEnumerable first, params object[] second)
{
    return second.Concat(first.OfType<object>());
}
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> first, params T[] second)
{
    return second.Concat(first);
}

Elegante (bene, tranne per le versioni non generiche). Peccato che questi metodi non siano nel BCL.


Il primo metodo Prepend mi sta dando un errore. "'object []' non contiene una definizione per 'Concat' e il miglior sovraccarico del metodo di estensione 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'ha alcuni argomenti non validi "
Tim Newton,

@ TimNewton stai sbagliando. Chissà perché, come nessuno può dire da quel frammento di codice di errore. Protip: rileva sempre l'eccezione e esegui l'operazione ToString(), poiché acquisisce più dettagli dell'errore. Immagino che non sia stato include System.Linq;all'inizio del tuo file, ma chi lo sa? Prova a capirlo da solo, crea un prototipo minimo che mostri lo stesso errore se non puoi e poi chiedi aiuto in una domanda.

Ho appena copiato e incollato il codice sopra. Funzionano tutti bene tranne il Prepend che sta dando l'errore che ho citato. Non è un'eccezione di runtime è un'eccezione di tempo di compilazione.
Tim Newton,

@TimNewton: Non so di cosa stai parlando funziona perfettamente, ovviamente ti sbagli, ignora i cambiamenti nella modifica ora devo essere sulla buona strada, buona giornata, signore.

forse è qualcosa di diverso nel nostro ambiente. Sto usando VS2012 e .NET 4.5. Non perdere altro tempo su questo, ho solo pensato di sottolineare che c'è un problema con il codice sopra, anche se piccolo. Ho votato questa risposta comunque come utile. Grazie.
Tim Newton,


22

No, IEnumerable non supporta l'aggiunta di elementi ad esso.

La tua "alternativa" è:

var List = new List(items);
List.Add(otherItem);

4
Il tuo suggerimento non è l'unica alternativa. Ad esempio, ICollection e IList sono entrambe opzioni ragionevoli qui.
Jason,

6
Ma non puoi neanche istanziare.
Paul van Brenk,

16

Per aggiungere un secondo messaggio devi:

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})

1
Ma devi anche assegnare il risultato del metodo Concat all'oggetto items.
Tomasz Przychodzki,

1
Sì, Tomasz hai ragione. Ho modificato l'ultima espressione per assegnare valori concatenati agli articoli.
Aamol,

8

Non solo non è possibile aggiungere elementi come si afferma, ma se si aggiunge un elemento a List<T>(o praticamente a qualsiasi altra raccolta di sola lettura) per cui si dispone di un enumeratore esistente, l'enumeratore viene invalidato (generato InvalidOperationExceptionda allora in poi).

Se si stanno aggregando risultati da un tipo di query di dati, è possibile utilizzare il Concatmetodo di estensione:

Modifica: inizialmente ho usato l' Unionestensione nell'esempio, che non è proprio corretta. La mia applicazione lo utilizza ampiamente per assicurarsi che le query sovrapposte non duplicino i risultati.

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);

8

Vengo solo qui per dire che, oltre al Enumerable.Concatmetodo di estensione, sembra esserci un altro metodo chiamato Enumerable.Appendin .NET Core 1.1.1. Quest'ultimo consente di concatenare un singolo elemento in una sequenza esistente. Quindi la risposta di Aamol può anche essere scritta come

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Append(new T("msg2"));

Tuttavia, si noti che questa funzione non modificherà la sequenza di input, ma restituirà semplicemente un wrapper che mette insieme la sequenza data e l'elemento aggiunto.


4

Altri hanno già dato grandi spiegazioni sul perché non puoi (e non dovresti!) Essere in grado di aggiungere elementi a un IEnumerable. Aggiungerò che se stai cercando di continuare a scrivere codice su un'interfaccia che rappresenta una raccolta e desideri un metodo di aggiunta, dovresti scrivere il codice ICollectiono IList. Come ulteriore bonanza, queste interfacce implementano IEnumerable.


1

Puoi farlo.

//Create IEnumerable    
IEnumerable<T> items = new T[]{new T("msg")};

//Convert to list.
List<T> list = items.ToList();

//Add new item to list.
list.add(new T("msg2"));

//Cast list to IEnumerable
items = (IEnumerable<T>)items;

8
A questa domanda è stata data una risposta più completa 5 anni fa. Guarda la risposta accettata come esempio di una buona risposta a una domanda.
piccione

1
L'ultima riga era: items = (IEnumerable <T>) list;
Belen Martin,

0

Il modo più semplice per farlo è semplicemente

IEnumerable<T> items = new T[]{new T("msg")};
List<string> itemsList = new List<string>();
itemsList.AddRange(items.Select(y => y.ToString()));
itemsList.Add("msg2");

Quindi è possibile restituire l'elenco come IEnumerable anche perché implementa l'interfaccia IEnumerable


0

Se crei il tuo oggetto come IEnumerable<int> Ones = new List<int>(); Allora puoi farlo((List<int>)Ones).Add(1);


-8

Certo, puoi (lascio da parte la tua T-business):

public IEnumerable<string> tryAdd(IEnumerable<string> items)
{
    List<string> list = items.ToList();
    string obj = "";
    list.Add(obj);

    return list.Select(i => i);
}
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.