Como posso adicionar um item a uma coleção IEnumerable <T>?

405

Minha pergunta como título acima. Por exemplo,

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

mas afinal ele tem apenas 1 item dentro.

Podemos ter um método como items.Add(item)?

como o List<T>

ldsenow
fonte
4
IEnumerable<T>destina-se apenas à consulta de coleções. É a espinha dorsal da estrutura LINQ. É sempre uma abstração de alguma outra coleção como Collection<T>, List<T>ou Array. A interface fornece apenas um GetEnumeratormétodo que retorna uma instância IEnumerator<T>que permite a caminhada da coleção estendida, um item de cada vez. Os métodos de extensão LINQ são limitados por essa interface. IEnumerable<T>foi projetado para ser lido somente porque pode representar uma agregação de partes de várias coleções.
Jordan
3
Para este exemplo, basta declarar em IList<T>vez de IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888 7/17

Respostas:

480

Você não pode, porque IEnumerable<T>não representa necessariamente uma coleção à qual itens podem ser adicionados. De fato, não representa necessariamente uma coleção! Por exemplo:

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

O que você pode fazer, no entanto, é criar um novo IEnumerable objeto (de tipo não especificado), que, quando enumerado, fornecerá todos os itens do antigo, além de alguns dos seus. Você usa Enumerable.Concatpara isso:

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

Isso não altera o objeto da matriz (você não pode inserir itens nas matrizes, de qualquer maneira). Mas ele criará um novo objeto que listará todos os itens da matriz e, em seguida, "Foo". Além disso, esse novo objeto acompanhará as alterações na matriz (ou seja, sempre que você a enumerar, você verá os valores atuais dos itens).

Pavel Minaev
fonte
3
Criar uma matriz para adicionar um único valor é um pouco alto na sobrecarga. É um pouco mais reutilizável definir um método de extensão para um único valor Concat.
JaredPar 31/07/2009
4
Depende - pode valer a pena, mas sou tentado a chamá-lo de otimização prematura, a menos que Concatseja chamado repetidamente em um loop; e se for, você tem problemas maiores de qualquer maneira, porque toda vez que Concatobtém mais uma camada de enumerador - portanto, ao final, a chamada única MoveNextresultará em uma cadeia de chamadas igual em comprimento ao número de iterações.
Pavel Minaev 31/07/2009
2
@ Pavel, heh. Normalmente, não é a sobrecarga de memória da criação da matriz que me incomoda. É a digitação extra que acho irritante :).
31410 JaredPar
2
Então, eu entendo um pouco mais o que o IEnumerable faz. Deve ser vista apenas não pretende ser modificada. Obrigado pessoal
ldsenow 31/07/2009
7
Eu tinha uma solicitação de recurso do Connect para açúcar sintático IEnumerable<T>no C #, incluindo sobrecarga, operator+já que Concat(a propósito, você sabe que no VB é possível indexar IEnumerablecomo se fosse um array - ele usa ElementAtsob o capô): connect.microsoft.com / VisualStudio / feedback /… . Se também recebermos mais duas sobrecargas Concatpara pegar um único item à esquerda e à direita, isso reduziria a digitação para xs +=x - então vá cutucar Mads (Torgersen) para priorizar isso no C # 5.0 :)
Pavel Minaev
98

O tipo IEnumerable<T>não suporta essas operações. O objetivo da IEnumerable<T>interface é permitir que um consumidor visualize o conteúdo de uma coleção. Não para modificar os valores.

Ao executar operações como .ToList (). Add (), você cria um novo List<T>e adiciona um valor a essa lista. Não tem conexão com a lista original.

O que você pode fazer é usar o método Add extension para criar um novo IEnumerable<T>com o valor agregado.

items = items.Add("msg2");

Mesmo nesse caso, ele não modificará o IEnumerable<T>objeto original . Isso pode ser verificado mantendo uma referência a ele. Por exemplo

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

Após esse conjunto de operações, a variável temp ainda fará referência a um enumerável com um único elemento "foo" no conjunto de valores, enquanto os itens farão referência a um enumerável diferente com os valores "foo" e "bar".

EDITAR

Eu sempre esqueço que o Add não é um método de extensão típico, IEnumerable<T>porque é um dos primeiros que eu acabo definindo. Aqui está

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}
JaredPar
fonte
12
Isso é chamado Concat:)
Pavel Minaev
3
@ Pavel, obrigado por apontar isso. Mesmo o Concat não funcionará porque é necessário outro IEnumerable <T>. Colei o método de extensão típico que defino em meus projetos.
31410 JaredPar
10
No Addentanto, eu não chamaria isso , porque Addpraticamente qualquer outro tipo de .NET (não apenas coleções) modifica a coleção no local. Talvez With? Ou poderia ser apenas mais uma sobrecarga de Concat.
Pavel Minaev 31/07/2009
4
Concordo que a Concatsobrecarga provavelmente seria uma escolha melhor, pois Addgeralmente implica mutabilidade.
Dan Abramov 27/07
15
Que tal modificar o corpo return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker
58

Você já pensou em usar interfaces ICollection<T>ou IList<T>, em vez disso, elas existem pelo mesmo motivo que você deseja ter um Addmétodo em uma IEnumerable<T>.

IEnumerable<T>é usado para 'marcar' um tipo como sendo ... bem, enumerável ou apenas uma sequência de itens sem necessariamente garantir que o objeto subjacente real suporte a adição / remoção de itens. Lembre-se também de que essas interfaces são implementadas IEnumerable<T>para que você obtenha todos os métodos de extensão com os IEnumerable<T>quais também se relaciona.

Abhijeet Patel
fonte
31

Alguns métodos de extensão curtos IEnumerablee IEnumerable<T>agradáveis ​​estão disponíveis para mim:

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 (bem, exceto para as versões não genéricas). Pena que esses métodos não estão no BCL.


fonte
O primeiro método de pré-adição está me dando um erro. "'object []' não contém uma definição para 'Concat' e a melhor sobrecarga do método de extensão 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'possui alguns argumentos inválidos "
Tim Newton
@ TimNewton, você está fazendo errado. Quem sabe o porquê, como ninguém pode dizer a partir desse trecho de código de erro. Protip: sempre pegue a exceção e faça uma ToString(), pois captura mais detalhes do erro. Eu acho que você não estava include System.Linq;no topo do seu arquivo, mas quem sabe? Tente descobrir por conta própria, crie um protótipo mínimo que exiba o mesmo erro, se você não puder, e peça ajuda em uma pergunta.
Acabei de copiar e colar o código acima. Todos eles funcionam bem, exceto o Prefpend, que está dando o erro que eu mencionei. Não é uma exceção de tempo de execução, é uma exceção de tempo de compilação.
Tim Newton
@ TimNewton: Não sei do que você está falando. Funciona perfeitamente. Obviamente, você está enganado, ignora as alterações na edição. Agora devo estar no meu caminho, bom dia, senhor.
talvez seja algo diferente no nosso ambiente. Estou usando o VS2012 e .NET 4.5. Não perca mais tempo com isso, apenas pensei em apontar que há um problema com o código acima, embora seja pequeno. De qualquer forma, votei de maneira positiva nesta resposta como útil. obrigado.
Tim Tim
25

No .net Core, existe um método Enumerable.Appendque faz exatamente isso.

O código fonte do método está disponível no GitHub. .... A implementação (mais sofisticada do que as sugestões em outras respostas) vale a pena dar uma olhada :).

JanDotNet
fonte
3
Isso não é apenas essencial, mas também a estrutura 4.7.1 e mais recente: docs.microsoft.com/en-us/dotnet/api/…
sschoof
Observe que o Prepend
aloisdg movendo-se para codidact.com
22

Não, o IEnumerable não oferece suporte à adição de itens.

Você 'alternativa' é:

var List = new List(items);
List.Add(otherItem);
Paul van Brenk
fonte
4
Sua sugestão não é a única alternativa. Por exemplo, ICollection e IList são opções razoáveis ​​aqui.
jason
6
Mas você também não pode instanciar.
Paul van Brenk
16

Para adicionar a segunda mensagem, você precisa -

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})
Aamol
fonte
11
Mas você também precisa atribuir o resultado do método Concat ao objeto de itens.
Tomasz Przychodzki
11
Sim, Tomasz, você está certo. Modifiquei a última expressão para atribuir valores concatenados aos itens.
Aamol
8

Não apenas você não pode adicionar itens como você declara, mas se você adicionar um item a List<T>(ou praticamente qualquer outra coleção não somente leitura) para a qual você tem um enumerador existente, o enumerador será invalidado (será lançado a InvalidOperationExceptionpartir de então).

Se você estiver agregando resultados de algum tipo de consulta de dados, poderá usar o Concatmétodo de extensão:

Editar: originalmente usei a Unionextensão no exemplo, o que não é realmente correto. Meu aplicativo o usa extensivamente para garantir que as consultas sobrepostas não duplicem os resultados.

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);
Sam Harwell
fonte
8

Eu só vim aqui para dizer que, além do Enumerable.Concatmétodo de extensão, parece haver outro método nomeado Enumerable.Appendno .NET Core 1.1.1. O último permite concatenar um único item para uma sequência existente. Portanto, a resposta de Aamol também pode ser escrita como

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

Ainda assim, observe que esta função não altera a sequência de entrada, apenas retorna um wrapper que coloca a sequência fornecida e o item anexado.

CXuesong
fonte
4

Outros já deram grandes explicações sobre por que você não pode (e não deveria!) Ser capaz de adicionar itens a um IEnumerable. Acrescentarei apenas que, se você deseja continuar codificando para uma interface que representa uma coleção e deseja um método add, deve codificar para ICollectionor IList. Como uma vantagem adicional, essas interfaces são implementadas IEnumerable.

Jason
fonte
1

você consegue fazer isso.

//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
fonte
8
Esta pergunta foi respondida de uma maneira mais completa há 5 anos. Veja a resposta aceita como um exemplo de uma boa resposta a uma pergunta.
pquest
11
A última linha foi: items = (IEnumerable <T>) list;
Belen Martin
0

A maneira mais fácil de fazer isso é simplesmente

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

Em seguida, você pode retornar a lista como IEnumerable também porque implementa a interface IEnumerable

Juha Sinkkonen
fonte
0

Se você criar seu objeto como IEnumerable<int> Ones = new List<int>(); Então você pode fazer((List<int>)Ones).Add(1);

Erik Hosszú
fonte
-8

Claro, você pode (estou deixando seu negócio de lado):

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

    return list.Select(i => i);
}
Just N Observer
fonte