Как я могу добавить элемент в коллекцию IEnumerable <T>?

405

Мой вопрос как заголовок выше. Например,

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

но в конце концов он имеет только 1 предмет внутри.

Можем ли мы иметь такой метод items.Add(item)?

словно List<T>

ldsenow
источник
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"));
NightOwl888

Ответы:

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
@ Павел, хе. Обычно меня беспокоит не объем памяти, создаваемый массивом. Это лишняя печать, которую я нахожу раздражающей :).
JaredPar
2
Так что я немного больше понимаю, что делает IEnumerable. Следует только просматривать, а не изменять. Спасибо, ребята
ldsenow
7
У меня был запрос функции Connect для синтаксического сахара для IEnumerable<T>C #, включая перегрузку, operator+как Concat(кстати, вы знаете, что в VB вы можете индексировать, IEnumerableкак если бы это был массив - он использует ElementAtпод капотом): connect.microsoft.com / VisualStudio / обратная связь /… . Если мы получим еще две перегрузки для того, Concatчтобы взять один элемент как слева, так и справа, это приведет к сокращению набора текста xs +=x - так что, пошлите в Mads (Torgersen), чтобы расставить приоритеты в C # 5.0 :)
Павел Минаев,
98

Тип IEnumerable<T>не поддерживает такие операции. Назначение IEnumerable<T>интерфейса - позволить потребителю просматривать содержимое коллекции. Не изменять значения.

Когда вы выполняете такие операции, как .ToList (). Add (), вы создаете новый List<T>и добавляете значение в этот список. Это не имеет никакого отношения к первоначальному списку.

Что вы можете сделать, это использовать метод Add extension, чтобы создать новый IEnumerable<T>с добавленной стоимостью.

items = items.Add("msg2");

Даже в этом случае он не изменит оригинальный IEnumerable<T>объект. Это можно проверить, сохранив ссылку на него. Например

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

После этого набора операций переменная temp по-прежнему будет ссылаться только на перечислимое значение с одним элементом «foo» в наборе значений, в то время как элементы будут ссылаться на другое перечисляемое значение со значениями «foo» и «bar».

РЕДАКТИРОВАТЬ

Я постоянно забываю, что 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;
}
JaredPar
источник
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);
}

Элегантный (ну, за исключением неуниверсальных версий). Жаль, что эти методы не в BCL.


источник
Первый метод Prepend дает мне ошибку. "'object []' не содержит определения для 'Concat' и лучшей перегрузки метода расширения 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'имеет недопустимые аргументы "
Тим Ньютон,
@ Тимньютон, ты делаешь это неправильно. Кто знает почему, как никто не может сказать из этого фрагмента кода ошибки. Подсказка: всегда перехватывайте исключение и делайте с ToString()ним исключение , так как это фиксирует больше деталей об ошибках. Я предполагаю, что вы не include System.Linq;в верхней части вашего файла, но кто знает? Попробуйте выяснить это самостоятельно, создайте минимальный прототип, который показывает ту же ошибку, если вы не можете, а затем попросите помощи в вопросе.
Я просто скопировал и вставил код выше. Все они работают нормально, за исключением Prepend, которая дает ошибку, о которой я упоминал. Это не исключение времени выполнения, это исключение времени компиляции.
Тим Ньютон
@TimNewton: я не знаю, о чем вы говорите, это работает отлично, вы, очевидно, ошибаетесь, игнорируйте изменения в редактировании, теперь я должен быть в пути, добрый день, сэр.
может быть, что-то другое в нашей среде. Я использую VS2012 и .NET 4.5. Не тратьте больше времени на это, я просто подумал, что могу указать на проблему с приведенным выше кодом, хотя и небольшую. Я все равно проголосовал за этот ответ, так как он полезен. Спасибо.
Тим Ньютон
25

В .net Core есть метод, Enumerable.Appendкоторый делает именно это.

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

JanDotNet
источник
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")})
Aamol
источник
1
Но вам также нужно присвоить результат метода Concat объекту items.
Томаш Пшиходзки
1
Да, Томаш, ты прав. Я изменил последнее выражение, чтобы назначить объединенные значения для элементов.
Aamol
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"));

Тем не менее, обратите внимание, что эта функция не изменит последовательность ввода, она просто вернет оболочку, которая соединит данную последовательность и добавленный элемент вместе.

CXuesong
источник
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;
Lrodriguez84
источник
8
На этот вопрос более полно ответили 5 лет назад. Посмотрите на принятый ответ как на пример хорошего ответа на вопрос.
задание
1
Последняя строка была: items = (IEnumerable <T>) list;
Белен Мартин
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);
}
Просто N Наблюдатель
источник