Wie kann ich ein Element zu einer IEnumerable <T> -Sammlung hinzufügen?

405

Meine Frage als Titel oben. Zum Beispiel,

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

aber es hat doch nur 1 artikel drin.

Können wir eine Methode wie haben items.Add(item)?

wie List<T>

ldsenow
quelle
4
IEnumerable<T>ist nur zum Abfragen von Sammlungen gedacht. Es ist das Rückgrat für das LINQ-Framework. Es ist immer eine Abstraktion von einer anderen Sammlung wie Collection<T>, List<T>oder Array. Die Schnittstelle bietet nur eine GetEnumeratorMethode, die eine Instanz zurückgibt IEnumerator<T>, mit der die erweiterte Sammlung einzeln durchlaufen werden kann. Die LINQ-Erweiterungsmethoden sind durch diese Schnittstelle begrenzt. IEnumerable<T>ist schreibgeschützt, da es eine Ansammlung von Teilen mehrerer Sammlungen darstellen kann.
Jordan
3
Für dieses Beispiel deklarieren Sie einfach IList<T>anstelle von IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Antworten:

480

Sie können nicht, da IEnumerable<T>dies nicht unbedingt eine Sammlung darstellt, zu der Elemente hinzugefügt werden können. Tatsächlich handelt es sich nicht unbedingt um eine Sammlung! Zum Beispiel:

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??

Sie können jedoch ein neues IEnumerable Objekt (vom nicht angegebenen Typ) erstellen , das bei Aufzählung alle Elemente des alten sowie einige Ihrer eigenen Objekte bereitstellt. Sie verwenden Enumerable.Concatdafür:

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

Dadurch wird das Array-Objekt nicht geändert (Sie können ohnehin keine Elemente in Arrays einfügen). Es wird jedoch ein neues Objekt erstellt, das alle Elemente im Array auflistet, und dann "Foo". Darüber hinaus verfolgt dieses neue Objekt Änderungen im Array (dh, wann immer Sie es aufzählen, werden die aktuellen Werte von Elementen angezeigt).

Pavel Minaev
quelle
3
Das Erstellen eines Arrays zum Hinzufügen eines einzelnen Werts ist etwas aufwändig. Es ist etwas wiederverwendbarer, eine Erweiterungsmethode für einen Concat mit einem Wert zu definieren.
JaredPar
4
Es kommt darauf an - es mag sich lohnen, aber ich bin versucht, es als vorzeitige Optimierung zu bezeichnen, es sei denn, es Concatwird wiederholt in einer Schleife aufgerufen. und wenn Concatja, haben Sie ohnehin größere Probleme, weil Sie jedes Mal eine weitere Aufzählungsebene erhalten. Am Ende führt der einzelne Aufruf von zu MoveNexteiner Aufrufkette, deren Länge der Anzahl der Iterationen entspricht.
Pavel Minaev
2
@ Pavel, heh. Normalerweise ist es nicht der Speicheraufwand beim Erstellen des Arrays, der mich stört. Es ist die zusätzliche Eingabe, die ich nervig finde :).
JaredPar
2
Ich verstehe also ein bisschen mehr, was IEnumerable macht. Es sollte nur die Ansicht sein, nicht geändert zu werden. Vielen Dank Jungs
ldsenow
7
Ich hatte eine Connect-Funktionsanforderung für syntaktischen Zucker IEnumerable<T>in C #, einschließlich Überladung operator+als Concat(übrigens, wissen Sie, dass Sie in VB indizieren können, IEnumerableals wäre es ein Array - es wird ElementAtunter der Haube verwendet): connect.microsoft.com / VisualStudio / feedback /… . Wenn wir auch zwei weitere Überladungen bekommen Concat, um einen einzelnen Gegenstand sowohl links als auch rechts zu nehmen, würde dies die Eingabe reduzieren xs +=x - also stupse Mads (Torgersen) an, um dies in C # 5.0 zu priorisieren :)
Pavel Minaev
98

Der Typ IEnumerable<T>unterstützt solche Operationen nicht. Der Zweck der IEnumerable<T>Schnittstelle besteht darin, einem Verbraucher das Anzeigen des Inhalts einer Sammlung zu ermöglichen. Die Werte nicht ändern.

Wenn Sie Vorgänge wie .ToList (). Add () ausführen, erstellen Sie eine neue List<T>und fügen dieser Liste einen Wert hinzu. Es besteht keine Verbindung zur ursprünglichen Liste.

Was Sie tun können, ist die Erweiterungsmethode Hinzufügen, um eine neue IEnumerable<T>mit dem Mehrwert zu erstellen .

items = items.Add("msg2");

Auch in diesem Fall wird das ursprüngliche IEnumerable<T>Objekt nicht geändert . Dies kann überprüft werden, indem ein Verweis darauf gehalten wird. Zum Beispiel

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

Nach diesem Satz von Operationen verweist die Variable temp immer noch nur auf eine Aufzählung mit einem einzelnen Element "foo" im Wertesatz, während Elemente auf eine andere Aufzählung mit den Werten "foo" und "bar" verweisen.

BEARBEITEN

Ich vergesse immer wieder, dass Add keine typische Erweiterungsmethode ist, IEnumerable<T>da es eine der ersten ist, die ich am Ende definiere. Hier ist es

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}
JaredPar
quelle
12
Das heißt Concat:)
Pavel Minaev
3
@ Pavel, danke für den Hinweis. Selbst Concat funktioniert nicht, da ein anderes IEnumerable <T> erforderlich ist. Ich habe die typische Erweiterungsmethode eingefügt, die ich in meinen Projekten definiert habe.
JaredPar
10
Ich würde das jedoch nicht nennen Add, da Addbei praktisch jedem anderen .NET-Typ (nicht nur Sammlungen) die Sammlung an Ort und Stelle mutiert. Vielleicht With? Oder es könnte sogar nur eine weitere Überlastung sein Concat.
Pavel Minaev
4
Ich bin damit einverstanden, dass ConcatÜberlastung wahrscheinlich die bessere Wahl wäre, da dies Addnormalerweise Veränderlichkeit impliziert.
Dan Abramov
15
Was ist mit der Veränderung des Körpers return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker
58

Haben Sie darüber nachgedacht, stattdessen ICollection<T>oder IList<T>Schnittstellen zu verwenden, existieren diese genau aus dem Grund, aus dem Sie eine AddMethode für eine haben möchten IEnumerable<T>.

IEnumerable<T>wird verwendet, um einen Typ als ... gut, aufzählbar oder nur als eine Folge von Elementen zu markieren, ohne unbedingt zu garantieren, ob das eigentliche zugrunde liegende Objekt das Hinzufügen / Entfernen von Elementen unterstützt. Denken Sie auch daran, dass diese Schnittstellen implementiert werden, IEnumerable<T>sodass Sie auch alle Erweiterungsmethoden erhalten, mit denen Sie arbeiten IEnumerable<T>.

Abhijeet Patel
quelle
31

Ein paar kurze, süße Verlängerungsmethoden IEnumerableund IEnumerable<T>mach es für mich:

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

Elegant (naja, bis auf die nicht generischen Versionen). Schade, dass diese Methoden nicht in der BCL enthalten sind.


quelle
Die erste Prepend-Methode gibt mir einen Fehler. "'object []' enthält keine Definition für 'Concat' und die beste Überlastung der Erweiterungsmethode 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'hat einige ungültige Argumente "
Tim Newton
@ TimNewton du machst es falsch. Wer weiß warum, wie niemand an diesem Fehlercode erkennen kann. Protip: Fange immer die Ausnahme ab und mache eine ToString()darauf, da dadurch mehr Fehlerdetails erfasst werden. Ich würde vermuten, dass Sie nicht ganz include System.Linq;oben in Ihrer Akte standen, aber wer weiß? Versuchen Sie es selbst herauszufinden, erstellen Sie einen Mindestprototyp, der denselben Fehler aufweist, wenn Sie dies nicht können, und bitten Sie dann bei einer Frage um Hilfe.
Ich habe gerade den obigen Code kopiert und eingefügt. Alle funktionieren einwandfrei, mit Ausnahme des Prepend, das den von mir erwähnten Fehler ausgibt. Es ist keine Laufzeitausnahme, sondern eine Ausnahme zur Kompilierungszeit.
Tim Newton
@ TimNewton: Ich weiß nicht, wovon Sie sprechen. Es funktioniert perfekt. Sie irren sich offensichtlich. Ignorieren Sie die Änderungen in der Bearbeitung. Jetzt muss ich auf dem Weg sein, guten Tag, Sir.
Vielleicht ist es etwas anderes an unserer Umwelt. Ich verwende VS2012 und .NET 4.5. Verschwenden Sie keine Zeit mehr damit. Ich dachte nur, ich würde darauf hinweisen, dass es ein Problem mit dem obigen Code gibt, wenn auch ein kleines. Ich habe diese Antwort sowieso als nützlich bewertet. Vielen Dank.
Tim Newton
22

Nein, die IEnumerable unterstützt das Hinzufügen von Elementen nicht.

Ihre "Alternative" ist:

var List = new List(items);
List.Add(otherItem);
Paul van Brenk
quelle
4
Ihr Vorschlag ist nicht die einzige Alternative. Zum Beispiel sind ICollection und IList hier beide vernünftige Optionen.
Jason
6
Aber Sie können auch nicht instanziieren.
Paul van Brenk
16

Um eine zweite Nachricht hinzuzufügen, müssen Sie -

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})
Aamol
quelle
1
Sie müssen das Ergebnis der Concat-Methode jedoch auch dem Objekt object zuweisen.
Tomasz Przychodzki
1
Ja, Tomasz, du hast recht. Ich habe den letzten Ausdruck geändert, um Elementen verkettete Werte zuzuweisen.
Aamol
8

Sie können nicht nur keine Elemente hinzufügen, wie Sie angeben, sondern wenn Sie ein Element zu einer List<T>(oder so ziemlich jeder anderen nicht schreibgeschützten Sammlung) hinzufügen, für die Sie einen vorhandenen Enumerator haben, wird der Enumerator ungültig (wirft InvalidOperationExceptionfortan).

Wenn Sie Ergebnisse aus einer Datenabfrage aggregieren, können Sie die ConcatErweiterungsmethode verwenden:

Bearbeiten: Ich habe ursprünglich die UnionErweiterung im Beispiel verwendet, was nicht wirklich korrekt ist. Meine Anwendung verwendet es ausgiebig, um sicherzustellen, dass überlappende Abfragen keine doppelten Ergebnisse liefern.

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);
Sam Harwell
quelle
8

Ich komme nur hierher, um zu sagen, dass es neben der Enumerable.ConcatErweiterungsmethode eine andere Methode mit dem Namen Enumerable.Append.NET Core 1.1.1 zu geben scheint . Mit letzterem können Sie ein einzelnes Element mit einer vorhandenen Sequenz verknüpfen. So kann Aamols Antwort auch geschrieben werden als

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

Beachten Sie jedoch, dass diese Funktion die Eingabesequenz nicht ändert, sondern lediglich einen Wrapper zurückgibt, der die angegebene Sequenz und das angehängte Element zusammenfügt.

CXuesong
quelle
4

Andere haben bereits großartige Erklärungen gegeben, warum Sie nicht in der Lage sein können (und sollten!), Elemente zu einem hinzuzufügen IEnumerable. Ich will nur noch hinzufügen , dass , wenn Sie Codierung an eine Schnittstelle weiterhin suchen, der eine Sammlung und wollen ein Add - Methode darstellt, sollten Sie codieren ICollectionoder IList. Als zusätzliche Goldgrube implementieren diese Schnittstellen IEnumerable.

Jason
quelle
1

du kannst das.

//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;
Lrodriguez84
quelle
8
Diese Frage wurde vor 5 Jahren vollständiger beantwortet. Sehen Sie sich die akzeptierte Antwort als Beispiel für eine gute Antwort auf eine Frage an.
Anfrage
1
Die letzte Zeile war: items = (IEnumerable <T>) list;
Belen Martin
0

Der einfachste Weg, dies zu tun, ist einfach

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

Dann können Sie list als IEnumerable zurückgeben, auch weil es die IEnumerable-Schnittstelle implementiert

Juha Sinkkonen
quelle
0

Wenn Sie Ihr Objekt als IEnumerable<int> Ones = new List<int>(); Dann erstellen, können Sie dies tun((List<int>)Ones).Add(1);

Erik Hosszú
quelle
-8

Sicher können Sie (ich lasse Ihr T-Geschäft beiseite):

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

    return list.Select(i => i);
}
Nur N Beobachter
quelle