¿Cómo puedo agregar un elemento a una colección IEnumerable <T>?


405

Mi pregunta como título arriba. Por ejemplo,

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

pero después de todo, solo tiene 1 elemento dentro.

¿Podemos tener un método como items.Add(item)?

como el List<T>


44
IEnumerable<T>está destinado solo a consultar colecciones. Es la columna vertebral del marco LINQ. Siempre es una abstracción de alguna otra colección como Collection<T>, List<T>o Array. La interfaz solo proporciona un GetEnumeratormétodo que devuelve una instancia de IEnumerator<T>que permite recorrer la colección extendida un elemento a la vez. Los métodos de extensión LINQ están limitados por esta interfaz. IEnumerable<T>está diseñado para ser leído solo porque puede representar una agregación de partes de múltiples colecciones.
Jordan

3
Para este ejemplo, simplemente declare en IList<T>lugar de IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Respuestas:


480

No puede, porque IEnumerable<T>no representa necesariamente una colección a la que se pueden agregar elementos. De hecho, ¡no representa necesariamente una colección en absoluto! Por ejemplo:

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

Sin embargo, lo que puede hacer es crear un nuevo IEnumerable objeto (de tipo no especificado) que, cuando se enumera, proporcionará todos los elementos del anterior, más algunos propios. Usas Enumerable.Concatpara eso:

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

Esto no cambiará el objeto de matriz (de todos modos, no puede insertar elementos en las matrices). Pero creará un nuevo objeto que enumerará todos los elementos de la matriz y luego "Foo". Además, ese nuevo objeto hará un seguimiento de los cambios en la matriz (es decir, cada vez que lo enumere, verá los valores actuales de los elementos).


3
Crear una matriz para agregar un valor único es un poco alto en la sobrecarga. Es un poco más reutilizable definir un método de extensión para un solo valor Concat.
JaredPar

44
Depende, puede valer la pena, pero estoy tentado a llamarlo optimización prematura a menos que Concatse llame repetidamente en un ciclo; y si es así, tiene problemas más grandes de todos modos, porque cada vez que Concatobtiene una capa más de enumerador, por lo que al final de la misma, la llamada única MoveNextdará como resultado una cadena de llamadas de igual longitud que el número de iteraciones.
Pavel Minaev el

2
@Pavel, je. Por lo general, no es la sobrecarga de memoria de crear la matriz lo que me molesta. Es el tipeo extra que me molesta :).
JaredPar

2
Entonces entiendo un poco más lo que hace IEnumerable. Se debe ver solo no tener la intención de ser modificado. Gracias chicos
ldsenow

77
Tuve una solicitud de función Connect para el azúcar sintáctico IEnumerable<T>en C #, incluida la sobrecarga operator+como Concat(por cierto, ¿sabes que en VB, puedes indexar IEnumerablecomo si fuera una matriz? Se usa ElementAtbajo el capó): connect.microsoft.com / VisualStudio / feedback /… . Si también obtenemos dos sobrecargas más Concatpara tomar un solo elemento tanto a la izquierda como a la derecha, esto reduciría el tipeo para xs +=x , así que ve a meter a Mads (Torgersen) para priorizar esto en C # 5.0 :)
Pavel Minaev

98

El tipo IEnumerable<T>no admite tales operaciones. El propósito de la IEnumerable<T>interfaz es permitir que un consumidor vea el contenido de una colección. No modificar los valores.

Cuando realiza operaciones como .ToList (). Add (), está creando una nueva List<T>y agregando un valor a esa lista. No tiene conexión con la lista original.

Lo que puede hacer es usar el método Agregar extensión para crear uno nuevo IEnumerable<T>con el valor agregado.

items = items.Add("msg2");

Incluso en este caso no modificará el IEnumerable<T>objeto original . Esto se puede verificar manteniendo una referencia a él. Por ejemplo

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

Después de este conjunto de operaciones, la variable temp solo hará referencia a un enumerable con un solo elemento "foo" en el conjunto de valores, mientras que los elementos harán referencia a un enumerable diferente con los valores "foo" y "bar".

EDITAR

Continuamente olvido que Agregar no es un método de extensión típico IEnumerable<T>porque es uno de los primeros que termino definiendo. Aquí está

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

12
Eso se llama Concat:)
Pavel Minaev

3
@Pavel, gracias por señalarlo. Sin embargo, incluso Concat no funcionará porque requiere otro IEnumerable <T>. Pegué el método de extensión típico que defino en mis proyectos.
JaredPar

10
Sin Addembargo, no lo llamaría porque Addprácticamente en cualquier otro tipo .NET (no solo en colecciones) muta la colección en el lugar. Tal vez With? O incluso podría ser solo otra sobrecarga de Concat.
Pavel Minaev el

44
Estoy de acuerdo en que la Concatsobrecarga probablemente sería una mejor opción, ya que Addgeneralmente implica mutabilidad.
Dan Abramov

15
¿Qué hay de modificar el cuerpo a return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker

58

¿Ha considerado usar interfaces ICollection<T>o IList<T>, en cambio, existen por la misma razón que desea tener un Addmétodo en un IEnumerable<T>.

IEnumerable<T>se usa para 'marcar' un tipo como ... bueno, enumerable o simplemente una secuencia de elementos sin necesariamente garantizar si el objeto subyacente real admite la adición / eliminación de elementos. También recuerde que estas interfaces se implementan IEnumerable<T>para que también obtenga todos los métodos de extensiones que obtiene IEnumerable<T>.


31

Un par de métodos de extensión cortos y dulces IEnumerabley IEnumerable<T>hazlo por mí:

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

Elegante (bueno, excepto las versiones no genéricas). Lástima que estos métodos no están en el BCL.


El primer método de anteponer me está dando un error. "'object []' no contiene una definición para 'Concat' y la mejor sobrecarga del método de extensión 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'tiene algunos argumentos no válidos "
Tim Newton

@TimNewton lo estás haciendo mal. Quién sabe por qué, como nadie puede deducir de ese fragmento de código de error. Protip: siempre capta la excepción y haz un ToString()sobre, ya que captura más detalles de error. Supongo que no lo hiciste include System.Linq;en la parte superior de tu archivo, pero ¿quién sabe? Intenta resolverlo por tu cuenta, crea un prototipo mínimo que muestre el mismo error si no puedes y luego pide ayuda en una pregunta.

Acabo de copiar y pegar el código anterior. Todos ellos funcionan bien, excepto el Prepend que está dando el error que mencioné. No es una excepción de tiempo de ejecución, es una excepción de tiempo de compilación.
Tim Newton

@TimNewton: No sé de qué está hablando, funciona perfectamente, obviamente está equivocado, ignore los cambios en la edición, ahora debo estar en camino, buen día, señor.

Tal vez es algo diferente sobre nuestro medio ambiente. Estoy usando VS2012 y .NET 4.5. No pierda más tiempo en esto, solo pensé en señalar que hay un problema con el código anterior, aunque sea pequeño. He votado esta respuesta de todos modos porque es útil. Gracias.
Tim Newton


22

No, el IEnumerable no admite agregarle elementos.

Su 'alternativa' es:

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

44
Su sugerencia no es la única alternativa. Por ejemplo, ICollection e IList son opciones razonables aquí.
jason

66
Pero tampoco puedes crear instancias.
Paul van Brenk el

16

Para agregar un segundo mensaje necesita:

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

1
Pero también debe asignar el resultado del método Concat al objeto de elementos.
Tomasz Przychodzki

1
Sí, Tomasz tienes razón. He modificado la última expresión para asignar valores concatenados a elementos.
Aamol

8

No solo no puede agregar elementos como List<T>dice , sino que si agrega un elemento a una (o prácticamente cualquier otra colección que no sea de solo lectura) para la que tiene un enumerador existente, el enumerador se invalida (se lanza a InvalidOperationExceptionpartir de ese momento).

Si está agregando resultados de algún tipo de consulta de datos, puede usar el Concatmétodo de extensión:

Editar: Originalmente usé la Unionextensión en el ejemplo, que no es realmente correcta. Mi aplicación lo utiliza ampliamente para asegurar que las consultas superpuestas no dupliquen los resultados.

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

8

Solo vine a decir que, aparte del Enumerable.Concatmétodo de extensión, parece haber otro método nombrado Enumerable.Appenden .NET Core 1.1.1. Este último le permite concatenar un solo elemento a una secuencia existente. Entonces la respuesta de Aamol también se puede escribir como

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

Aún así, tenga en cuenta que esta función no cambiará la secuencia de entrada, solo devolverá un contenedor que juntará la secuencia dada y el elemento adjunto.


4

Otros ya han dado excelentes explicaciones sobre por qué no puede (y no debería) poder agregar elementos a un IEnumerable. Solo agregaré que si desea continuar codificando en una interfaz que representa una colección y desea un método de agregar, debe codificar ICollectiono IList. Como una bonanza adicional, se implementan estas interfaces IEnumerable.


1

Puedes hacerlo.

//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
Esta pregunta fue respondida de manera más completa hace 5 años. Mire la respuesta aceptada como un ejemplo de una buena respuesta a una pregunta.
pquest

1
La última línea fue: elementos = (IEnumerable <T>) lista;
Belen Martin el

0

La forma más fácil de hacerlo es simplemente

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

Entonces puede devolver la lista como IEnumerable también porque implementa la interfaz IEnumerable


0

Si crea su objeto como IEnumerable<int> Ones = new List<int>(); Entonces puede hacer((List<int>)Ones).Add(1);


-8

Claro, puedes (estoy dejando de lado tu negocio T):

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

    return list.Select(i => i);
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.