2009-07-31 2 views
298

Мой вопрос как название выше. Например,Как добавить элемент в коллекцию IEnumerable <T>?

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

, но в конце концов он имеет только 1 элемент внутри.

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

как List<T>

+0

'IEnumerable ' предназначен только для запросов к коллекциям. Это основа для структуры LINQ. Это всегда абстракция какой-либо другой коллекции, такой как 'Collection ', 'Список ' или 'Array'. Интерфейс предоставляет только метод «GetEnumerator», который возвращает экземпляр «IEnumerator », который позволяет перемещать расширяющуюся коллекцию по одному элементу за раз. Методы расширения LINQ ограничены этим интерфейсом. 'IEnumerable ' предназначен для чтения только потому, что он может представлять собой агрегацию частей из нескольких коллекций. – Jordan

+1

В этом примере просто объявите 'IList ' вместо 'IEnumerable ': 'IList items = new T [] {new T (" msg ")}; items.Add (новый T ("msg2")); ' – NightOwl888

ответ

357

Вы не можете, потому что IEnumerable<T> не обязательно представляет собой коллекцию, в которой могут быть добавлены элементы. На самом деле, это не обязательно представляет собой коллекцию!Например:

IEnumerable<string> ReadLines() 
{ 
    string s; 
    do 
    { 
      s = Console.ReadLine(); 
      yield return s; 
    } while (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

+3

Это зависит - возможно, это стоит того, но я склонен называть его преждевременной оптимизацией, если «Конкат» не вызывается в цикле повторно; и если это так, у вас есть большие проблемы в любом случае, потому что каждый раз, когда вы «Concat», вы получаете еще один слой перечислителя, поэтому к концу этого один вызов «MoveNext» приведет к цепочке вызовов, равных по длине к числу итераций. –

+1

@Pavel, хех. Обычно это не из-за нехватки памяти для создания массива, который меня беспокоит. Это лишний набор текста, который я нахожу раздражающим :). – JaredPar

12

Н.О. IEnumerable doens't поддерживает добавление в нее элементов.

«Альтернатива» у вас есть.

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

Ваше предложение не является единственной альтернативой. Например, ICollection и IList являются разумными вариантами. – jason

+5

Но вы также не можете создать экземпляр. –

+4

Конечно, вы не можете создать экземпляр - это интерфейсы. –

74

Тип 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"); 

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

EDIT

Я contstantly забывать, что Занести не типичный метод расширения на 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; 
} 
+9

Это называется 'Concat' :) –

+1

@Pavel, спасибо, что указали это. Даже Concat не будет работать, потому что требуется другой IEnumerable . Я применил типичный метод расширения, который я определяю в своих проектах. – JaredPar

+7

Я бы не назвал это 'Add', потому что' Add' практически для любого другого типа .NET (а не только для коллекций) мутирует коллекцию на месте. Может быть, «С»? Или это может быть просто еще одна перегрузка «Concat». –

8

Не только вы можете не добавлять элементы, как вы утверждаете, но если добавить элемент в List<T> (или почти любой другой, не только для чтения коллекции), что у вас есть существующий энумератор, перечислитель недействителен (сбрасывает затем InvalidOperationException).

Если вы агрегирование результаты какого-либо типа запроса данных, вы можете использовать метод Concat расширения:

Edit: Первоначально я использовал Union расширение в примере, который не является действительно правильным. Мое приложение широко использует его, чтобы убедиться, что совпадающие запросы не дублируют результаты.

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

Другие уже дали большие объяснения относительно того, почему вы не можете (и не должны!) Иметь возможность добавлять предметы в IEnumerable. Я добавлю, что если вы хотите продолжить кодирование интерфейса, представляющего коллекцию, и хотите добавить метод, вы должны ввести код ICollection или IList. В качестве добавленного достоинства эти интерфейсы реализуют IEnumerable.

41

Рассматривали ли вы с помощью ICollection<T> или IList<T> интерфейсов вместо этого, они существуют по той причине, что вы хотите иметь метод «Добавить» на IEnumerable < Т >. IEnumerable <T> используется, чтобы «маркировать» тип как ... ну .. перечислимый или просто последовательность элементов без каких-либо гарантий того, поддерживает ли реальный базовый объект добавление/удаление элементов. Также помните, что эти интерфейсы реализуют IEnumerable <T>, поэтому вы получаете все методы расширения, которые вы получаете с IEnumerable <T>.

25

Пару короткие, сладкие методы расширения на 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); 
} 

Elegant (ну, для нетипичных версий, кроме). Слишком плохо, что эти методы не входят в BCL.

+0

Первый метод Prepend дает мне ошибку. «Объект []» не содержит определения для «Конкат» и лучший метод перегрузки расширения. System.Linq.Enumerable.Concat (System.Collections.Generic.IEnumerable , System.Collections.Generic.IEnumerable ) ' имеет некоторые недопустимые аргументы " –

+0

@TimNewton вы делаете это неправильно. Кто знает, почему, как никто не может сказать из этого фрагмента кода ошибки. Protip: всегда улавливайте исключение и выполняйте 'ToString()' на нем, так как это отображает больше деталей об ошибках. Я бы предположил, что вы не включили System.Linq; 'в верхней части файла, но кто знает? Попытайтесь выяснить это самостоятельно, создайте минимальный прототип, который проявляет ту же ошибку, если вы не можете, а затем попросите о помощи в вопросе. – Will

+0

Я только что скопировал и вставил код выше. Все они работают нормально, кроме Prepend, который дает ошибку, о которой я упоминал. Это не исключение из среды выполнения, исключающее исключение во время компиляции. –

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; 
+7

Этот вопрос был дан более совершенным образом 5 лет назад. Посмотрите на принятый ответ в качестве примера хорошего ответа на вопрос. – pquest

0

Easyest способ сделать это просто

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 интерфейс

9

Чтобы добавить второе сообщение вам нужно -

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

Но вам также нужно присвоить результат метода Concat объекту items. –

+0

Да, Томаш, вы правы. Я изменил последнее выражение, чтобы назначить конкатенированные значения элементам. – Aamol

-4

Несомненно, вы можете (я оставляю ваш T-бизнес в стороне):

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

    return list.Select(i => i); 
} 
1

Я только что пришел сюда, чтобы сказать, что, помимо Enumerable.Concat метода расширения, кажется, есть еще один метод, названный Enumerable.Append в .NET Ядро 1.1.1. Последний позволяет объединить один элемент в существующую последовательность.Так ответ Aamol также можно записать в виде

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

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

3

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

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

Смежные вопросы