Jak mogę dodać element do kolekcji IEnumerable <T>?


405

Moje pytanie jako tytuł powyżej. Na przykład,

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

ale w końcu ma tylko 1 przedmiot.

Czy możemy mieć taką metodę items.Add(item)?

podobnie jak List<T>


4
IEnumerable<T>jest przeznaczony tylko do wysyłania zapytań do kolekcji. Jest to szkielet szkieletu LINQ. Zawsze jest abstrakcją z innej kolekcji, takich jak Collection<T>, List<T>lub Array. Interfejs udostępnia tylko GetEnumeratormetodę zwracającą instancję, IEnumerator<T>która umożliwia przechodzenie rozszerzonej kolekcji po jednym elemencie na raz. Interfejs rozszerzeń LINQ jest ograniczony przez ten interfejs. IEnumerable<T>jest przeznaczony tylko do odczytu, ponieważ może reprezentować agregację części wielu kolekcji.
Jordan

3
W tym przykładzie zadeklaruj IList<T>zamiast IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Odpowiedzi:


480

Nie możesz, ponieważ IEnumerable<T>niekoniecznie oznacza kolekcję, do której można dodać elementy. W rzeczywistości niekoniecznie reprezentuje kolekcję! Na przykład:

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

Możesz jednak utworzyć nowy IEnumerable obiekt (nieokreślonego typu), który po wyliczeniu dostarczy wszystkie elementy starego oraz niektóre własne. Używasz Enumerable.Concatdo tego:

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

To nie zmieni obiektu Array (nie można wstawiać elementy do do tablic, tak). Ale stworzy nowy obiekt, który wyświetli listę wszystkich elementów w tablicy, a następnie „Foo”. Ponadto ten nowy obiekt będzie śledził zmiany w tablicy (tzn. Ilekroć go wyliczysz, zobaczysz bieżące wartości elementów).


3
Tworzenie tablicy w celu dodania pojedynczej wartości jest nieco wysokie. Nieco bardziej nadaje się do ponownego zdefiniowania metody rozszerzenia dla pojedynczej wartości Concat.
JaredPar

4
To zależy - może warto, ale kusi mnie, by nazwać to przedwczesną optymalizacją, chyba że Concatjest wywoływana wielokrotnie; a jeśli tak, to i tak masz większe problemy, ponieważ za każdym razem Concatdostajesz jeszcze jedną warstwę modułu wyliczającego - więc na końcu pojedyncze wywołanie MoveNextspowoduje łańcuch połączeń równy długości iteracji.
Pavel Minaev

2
@Pavel, heh. Zwykle nie martwi mnie narzut pamięci związany z tworzeniem tablicy. To dodatkowe pisanie, które wydaje mi się denerwujące :).
JaredPar

2
Rozumiem więc trochę więcej, co robi IEnumerable. Powinien być wyświetlany, a nie tylko modyfikowany. Dziękuję chłopaki
ldsenow

7
Miałem żądanie funkcji Connect dla cukru syntaktycznego IEnumerable<T>w języku C #, w tym przeładowanie, operator+ponieważ Concat(nawiasem mówiąc, czy wiesz, że w VB możesz indeksować, IEnumerablejakby to była tablica - używa ElementAtpod maską): connect.microsoft.com / VisualStudio / feedback /… . Jeśli otrzymamy jeszcze dwa przeciążenia, Concataby wziąć pojedynczy przedmiot zarówno po lewej, jak i po prawej stronie, zmniejszy to pisanie do xs +=x - więc idź poke Mads (Torgersen), aby nadać temu priorytetowi w C # 5.0 :)
Pavel Minaev

98

Typ IEnumerable<T>nie obsługuje takich operacji. Celem IEnumerable<T>interfejsu jest umożliwienie konsumentowi zobaczyć zawartość kolekcji. Nie modyfikować wartości.

Wykonując operacje takie jak .ToList (). Add (), tworzysz nowy List<T>i dodajesz wartość do tej listy. Nie ma połączenia z oryginalną listą.

Możesz użyć metody Dodaj rozszerzenie, aby utworzyć nową IEnumerable<T>z wartością dodaną.

items = items.Add("msg2");

Nawet w tym przypadku nie zmodyfikuje oryginalnego IEnumerable<T>obiektu. Można to zweryfikować, trzymając odniesienie do niego. Na przykład

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

Po tym zestawie operacji zmienna temp będzie nadal odwoływała się do wyliczenia z pojedynczym elementem „foo” w zbiorze wartości, podczas gdy pozycje będą odwoływać się do innego wyliczenia z wartościami „foo” i „bar”.

EDYTOWAĆ

Ciągle zapominam, że Add nie jest typową metodą rozszerzenia, IEnumerable<T>ponieważ jest to jedna z pierwszych metod, które ostatecznie definiuję. Oto jest

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

12
To się nazywa Concat:)
Pavel Minaev,

3
@Pavel, dzięki za zwrócenie na to uwagi. Nawet Concat nie będzie działać, ponieważ wymaga innego IEnumerable <T>. Wkleiłem typową metodę rozszerzenia, którą zdefiniowałem w moich projektach.
JaredPar

10
Nie nazwałbym tego Addjednak, ponieważ Addpraktycznie na każdym innym typie .NET (nie tylko kolekcje) mutuje kolekcję w miejscu. Może With? Może to być nawet kolejne przeciążenie Concat.
Pavel Minaev

4
Zgadzam się, Concatże przeciążenie byłoby prawdopodobnie lepszym wyborem, ponieważ Addzwykle implikuje zmienność.
Dan Abramov

15
A co z modyfikowaniem ciała return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker

58

Czy zastanawiałeś się nad użyciem ICollection<T>lub IList<T>interfejsów, istnieją one z tego samego powodu, dla którego chcesz mieć Addmetodę na IEnumerable<T>.

IEnumerable<T>służy do „oznaczania” typu jako… cóż, policzalnego lub tylko sekwencji elementów bez konieczności zapewnienia jakiejkolwiek gwarancji, czy prawdziwy obiekt leżący u podstaw obsługuje dodawanie / usuwanie elementów. Pamiętaj również, że te interfejsy są implementowane, IEnumerable<T>dzięki czemu otrzymujesz również wszystkie metody rozszerzeń IEnumerable<T>.


31

Kilka krótkich, słodkich metod przedłużania IEnumerablei IEnumerable<T>zrób to dla mnie:

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

Elegancki (cóż, z wyjątkiem nietypowych wersji). Szkoda, że ​​tych metod nie ma w BCL.


Pierwsza metoda Prepend daje mi błąd. „” Obiekt [] ”nie zawiera definicji„ Concat ”, a najlepsza metoda rozszerzenia przeciąża„ System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'ma kilka niepoprawnych argumentów ”
Tim Newton,

@TimNewton robisz to źle. Kto wie dlaczego, skoro nikt nie wie na podstawie tego fragmentu kodu błędu. Protip: zawsze wychwytuj wyjątek i zrób ToString()na nim, ponieważ to przechwytuje więcej szczegółów błędów. Sądzę, że nie znalazłeś się include System.Linq;na początku pliku, ale kto wie? Spróbuj samemu to ustalić, stwórz minimalny prototyp, który pokazuje ten sam błąd, jeśli nie możesz, a następnie poproś o pomoc w pytaniu.

Właśnie skopiowałem i wkleiłem powyższy kod. Wszystkie działają poprawnie, z wyjątkiem Prepend, który daje błąd, o którym wspomniałem. To nie jest wyjątek czasu wykonywania, to wyjątek czasu kompilacji.
Tim Newton,

@ TimNewton: Nie wiem, o czym mówisz, działa idealnie, oczywiście się mylisz, ignorujesz zmiany w edycji, teraz muszę być w drodze, dzień dobry, proszę pana.

może to coś innego w naszym środowisku. Używam VS2012 i .NET 4.5. Nie marnuj już więcej czasu. Po prostu pomyślałem, że zwrócę uwagę na problem z powyższym kodem, choć niewielkim. Tak czy inaczej oceniłem tę odpowiedź jako przydatną. dzięki.
Tim Newton,


22

Nie IEnumerable nie obsługuje dodawania do niego elementów.

„Alternatywą” jest:

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

4
Twoja sugestia nie jest jedyną alternatywą. Na przykład ICollection i IList są tutaj rozsądnymi opcjami.
jason

6
Ale nie możesz również utworzyć instancji.
Paul van Brenk

16

Aby dodać drugą wiadomość, musisz:

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

1
Ale musisz także przypisać wynik metody Concat do obiektu przedmiotów.
Tomasz Przychodzki

1
Tak, Tomasz masz rację. Zmodyfikowałem ostatnie wyrażenie, aby przypisać konkatenowane wartości do elementów.
Aamol,

8

Nie tylko nie możesz dodawać elementów w taki sposób, jak podajesz, ale jeśli dodasz element do List<T>(lub prawie każdej innej kolekcji nie tylko do odczytu), dla której masz istniejący moduł wyliczający, moduł wyliczający jest unieważniany ( InvalidOperationExceptionod tego momentu wyrzuca ).

Jeśli agregujesz wyniki z pewnego rodzaju zapytania o dane, możesz użyć Concatmetody rozszerzenia:

Edycja: pierwotnie użyłem Unionrozszerzenia w tym przykładzie, co nie jest tak naprawdę poprawne. Moja aplikacja korzysta z niego w szerokim zakresie, aby nakładające się zapytania nie dublowały wyników.

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

8

Przyszedłem tutaj, aby powiedzieć, że oprócz Enumerable.Concatmetody rozszerzenia, wydaje się, że istnieje inna metoda o nazwie Enumerable.Appendw .NET Core 1.1.1. Ta ostatnia pozwala połączyć jeden element z istniejącą sekwencją. Tak więc odpowiedź Aamola można również zapisać jako

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

Pamiętaj jednak, że ta funkcja nie zmieni sekwencji wejściowej, po prostu zwróci opakowanie, które połączy daną sekwencję i dołączony element.


4

Inni podali już świetne wyjaśnienia dotyczące tego, dlaczego nie możesz (i nie powinieneś!) Mieć możliwości dodawania elementów do IEnumerable. Dodam tylko, że jeśli chcesz kontynuować kodowanie do interfejsu reprezentującego kolekcję i chcesz dodać metodę, powinieneś kodować do ICollectionlub IList. Jako dodatkowa bonanza implementują te interfejsy IEnumerable.


1

możesz to zrobić.

//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
Odpowiedź na to pytanie była pełniejsza 5 lat temu. Przyjrzyj się przyjętej odpowiedzi jako przykład dobrej odpowiedzi na pytanie.
pquest

1
Ostatni wiersz to: items = (IEnumerable <T>) lista;
Belen Martin,

0

Najłatwiejszym sposobem na to jest po prostu

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

Następnie możesz zwrócić listę jako IEnumerable, ponieważ implementuje interfejs IEnumerable


0

Jeśli utworzysz obiekt tak, jak IEnumerable<int> Ones = new List<int>(); możesz, możesz to zrobić((List<int>)Ones).Add(1);


-8

Jasne, że możesz (odkładam na bok swój biznes T):

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

    return list.Select(i => i);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.