Як я можу додати предмет до колекції IEnumerable <T>?


405

Моє запитання як назва вище. Наприклад,

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

але зрештою у нього є лише 1 предмет.

Чи можемо ми мати такий метод items.Add(item)?

як List<T>


4
IEnumerable<T>призначений лише для запитів колекцій. Він є основою для рамки LINQ. Це завжди абстракція який - то інший колекції , такі як Collection<T>, List<T>або Array. Інтерфейс надає лише GetEnumeratorметод, який повертає екземпляр, IEnumerator<T>який дозволяє ходити по колекції, що розширюється, по одному. Методи розширення LINQ обмежені цим інтерфейсом. IEnumerable<T>призначений для читання лише тому, що він може представляти собою сукупність частин декількох колекцій.
Йорданія

3
Для цього прикладу, просто IList<T>IEnumerable<T>IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
заявіть

Відповіді:


480

Ви не можете, оскільки IEnumerable<T>це не обов'язково представляє колекцію, до якої можна додавати елементи. Насправді це зовсім не обов'язково представляє колекцію! Наприклад:

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

Однак ви можете створити новий IEnumerable об'єкт (не визначеного типу), який при перерахунку надасть усі предмети старого, а також деякі власні. Ви використовуєте Enumerable.Concatдля цього:

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

Це не змінить об’єкт масиву (так чи інакше, ви не можете вставляти елементи в масиви). Але це створить новий об'єкт, який перелічить усі елементи в масиві, а потім "Foo". Крім того, цей новий об’єкт буде відслідковувати зміни в масиві (тобто, коли ви його перераховуєте, ви побачите поточні значення елементів).


3
Створення масиву для додавання єдиного значення трохи вище накладних. Визначити метод розширення для одного значення Concat трохи повторно.
JaredPar

4
Це залежить - можливо, того варто, але я спокушаюсь назвати це передчасною оптимізацією, якщо не Concatбуде викликано циклом повторно; і якщо це так, у вас все-таки виникають більші проблеми, тому що кожного разу ви Concatотримуєте ще один шар перелічувача - тож до кінця одного виклику в MoveNextрезультаті вийде ланцюжок викликів, рівний за довжиною кількості ітерацій.
Павло Мінаєв

2
@Pavel, хе. Зазвичай мене не турбує пам'ять, накладна на створення масиву. Це зайвий набір тексту, який мені здається роздратованим :).
JaredPar

2
Тож я трохи більше розумію, що робить IEnumerable. Він повинен бути представлений лише не має наміру змінюватися. Дякую, хлопці
ldsenow

7
У мене був запит на функцію Connect для синтаксичного цукру для IEnumerable<T>C #, включаючи перевантаження, operator+оскільки Concat(до речі, ви знаєте, що в VB ви можете проіндексувати IEnumerable, ніби це масив - він використовується ElementAtпід кришкою): connect.microsoft.com / VisualStudio / відгуки /… . Якщо ми також отримаємо ще два перевантаження, Concatщоб взяти один предмет як зліва, так і з правого боку, це призведе до скорочення набору тексту xs +=x - тому йдіть тицьком Мадс (Торгерсен), щоб надати цьому пріоритет на C # 5.0 :)
Павло Мінаєв,

98

Тип IEnumerable<T>не підтримує такі операції. Мета IEnumerable<T>інтерфейсу - дозволити споживачеві переглядати вміст колекції. Не змінювати значення.

Виконуючи такі дії, як .ToList (). Add (), ви створюєте новий List<T>і додаєте значення до цього списку. Він не має зв'язку з оригінальним списком.

Що ви можете зробити, це використовувати метод Додати розширення для створення нового IEnumerable<T>з доданою вартістю.

items = items.Add("msg2");

Навіть у цьому випадку він не змінює оригінальний IEnumerable<T>об'єкт. Це можна перевірити, посилаючись на нього. Наприклад

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

Після цього набору операцій змінна temp все ще посилатиметься лише на перелік з одним елементом "foo" у наборі значень, тоді як елементи будуть посилатись на інше число із значеннями "foo" та "bar".

EDIT

Я безперечно забуваю, що Add не є типовим методом розширення, IEnumerable<T>оскільки це один із перших, який я закінчую визначати. Ось

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

12
Так називається Concat:)
Павло Мінаєв

3
@Pavel, дякую, що вказали на це. Навіть Concat не буде працювати, хоча це вимагає ще одного IEnumerable <T>. Я вставив типовий метод розширення, який я визначаю у своїх проектах.
JaredPar

10
Я б це не називав Add, тому що Addпрактично будь-який інший тип .NET (не лише колекції) мутує колекцію на місці. Можливо With? Або це може бути просто чергове перевантаження Concat.
Павло Мінаєв

4
Я погоджуюсь, що Concatперевантаження може бути кращим вибором, оскільки, як Addправило, передбачає незмінність.
Дан Абрамов

15
Що з модифікацією організму return e.Concat(Enumerable.Repeat(value,1));?
Рой Тінкер

58

Чи розглядали ви замість цього використовувати інтерфейси ICollection<T>чи IList<T>інтерфейси, вони існують саме з тієї причини, що ви хочете мати Addметод на IEnumerable<T>.

IEnumerable<T>використовується для "позначення" типу як ... ну, перелічуваної чи просто послідовності елементів, не обов'язково надаючи жодних гарантій, чи підтримує реальний базовий об'єкт додавання / видалення елементів. Також пам’ятайте, що ці інтерфейси реалізуються, IEnumerable<T>щоб ви отримали всі розширення, які ви також отримуєте IEnumerable<T>.


31

Кілька коротких, солодких методів розширення IEnumerableі IEnumerable<T>зробіть це для мене:

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

Елегантний (ну, крім випадків, що не є загальними). Шкода, що ці методи відсутні в БКЛ.


Перший метод подання дає мені помилку. "'object []" не містить визначення поняття "Concat", а найкращий метод розширення перевантажує "System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <Tource>) 'має деякі невірні аргументи "
Тім Ньютон

@TimNewton ти робиш це неправильно. Хтозна чому, як ніхто не може сказати з цього фрагмента коду помилки. Підказка: завжди вибирайте виняток і виконайте ToString()його, оскільки це фіксує більше деталей помилок. Я б здогадувався, що ти не був include System.Linq;у верхній частині файлу, але хто знає? Спробуйте розібратися самостійно, створіть мінімальний прототип, який виявляє ту саму помилку, якщо ви не можете, а потім попросіть допомогу у питанні.

Я просто скопіював і вставив код вище. Усі вони працюють нормально, за винятком препенди, що дає помилку, про яку я згадав. Це не виняток із виконання, а виняток із компіляції.
Тім Ньютон

@TimNewton: Я не знаю, про що ти говориш, це працює чудово, ти, очевидно, помиляється, ігноруючи зміни в редагуванні, зараз я повинен бути на шляху, добрий день, сер.

можливо, це щось інше щодо нашого оточення. Я використовую VS2012 та .NET 4.5. Не витрачаючи більше часу на це, я просто думав, що зазначу, що з вищенаведеним кодом є проблеми, хоча і невелика. Цю відповідь я так чи інакше відхилив як корисну. Дякую.
Тім Ньютон

25

У .net Core є метод, Enumerable.Appendякий робить саме це.

Вихідний код методу доступний на GitHub. .... Реалізація (більш складна, ніж пропозиції в інших відповідях) варто переглянути :).


3
Це не лише в ядрі, а також в рамках 4.7.1 і новіших: docs.microsoft.com/en-us/dotnet/api/…
sschoof

Зауважте, що Prepend також існує: docs.microsoft.com/en-us/dotnet/api/…
aloisdg переходить на codidact.com

22

Ніякий IEnumerable не підтримує додавання до нього елементів.

Ви "альтернативою" є:

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

4
Ваша пропозиція - не єдина альтернатива. Наприклад, ICollection та IList - це обидва варіанти.
Язон

6
Але ви також не можете створити інстанцію.
Пол ван Бренк

16

Щоб додати друге повідомлення, потрібно -

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

1
Але вам також потрібно призначити результат методу Concat об'єктам елементів.
Tomasz Przychodzki

1
Так, Томаш, ти маєш рацію. Я змінив останній вираз, щоб призначити об'єднані значення елементам.
Аамол

8

Ви не тільки не можете додавати такі елементи, як ви заявляєте, але якщо ви додаєте елемент до List<T>(або майже будь-якої іншої нечитаної колекції), для якої у вас є існуючий перелік, перелік недійсний (перекидає InvalidOperationExceptionз цього моменту ).

Якщо ви агрегуєте результати за деяким типом запиту даних, ви можете використовувати Concatметод розширення:

Редагувати: Спочатку я використовував Unionрозширення в прикладі, що не дуже правильно. Мій додаток широко використовує його, щоб переконатися, що збіги запитів не дублюють результати.

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

8

Я просто прийшов сюди, щоб сказати, що окрім Enumerable.Concatметоду розширення, схоже, існує ще один метод, названий Enumerable.Appendу .NET Core 1.1.1. Останнє дозволяє об'єднати один елемент у існуючу послідовність. Тож відповідь Аамола також можна записати як

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

Однак зауважте, що ця функція не змінить послідовність введення, вона просто поверне обгортку, яка з'єднає задану послідовність і доданий елемент разом.


4

Інші вже давали чудові пояснення щодо того, чому ви не можете (і не повинні!) Мати змогу додавати елементи до IEnumerable. Я лише додам, що якщо ви хочете продовжити кодування до інтерфейсу, який представляє колекцію та хочете додати метод додавання, вам слід ввести код ICollectionабо IList. Як доданий бонанса, ці інтерфейси реалізуються IEnumerable.


1

ви можете це зробити.

//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
На це питання відповіли більш повно, 5 років тому. Подивіться на прийняту відповідь як на приклад хорошої відповіді на запитання.
pquest

1
Останній рядок: список елементів = (IEnumerable <T>);
Белен Мартін

0

Найпростіший спосіб зробити це просто

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

Тоді ви можете повернути список як IEnumerable ще й тому, що він реалізує інтерфейс IEnumerable


0

Якщо ви створюєте об'єкт так, як IEnumerable<int> Ones = new List<int>(); тоді, ви можете це зробити((List<int>)Ones).Add(1);


-8

Звичайно, ви можете (я залишаю ваш T-бізнес осторонь):

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

    return list.Select(i => i);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.