Qual é a melhor maneira de modificar uma lista em um loop ‘foreach’?

Um novo recurso no C # / .NET 4.0 é que você pode alterar seu enumerável em um foreach sem obter a exceção. Veja a input do blog de Paul Jackson Um Efeito Secundário Interessante da Simultaneidade: Removendo Itens de uma Coleção Enquanto Enumerando para obter informações sobre essa mudança.

Qual é a melhor maneira de fazer o seguinte?

 foreach(var item in Enumerable) { foreach(var item2 in item.Enumerable) { item.Add(new item2) } } 

Normalmente eu uso um IList como cache / buffer até o final do foreach , mas existe melhor maneira?

A coleção usada no foreach é imutável. Isso é muito por design.

Como diz no MSDN :

A instrução foreach é usada para percorrer a coleção para obter as informações desejadas, mas não pode ser usada para adicionar ou remover itens da coleção de origem para evitar efeitos colaterais imprevisíveis. Se você precisar adicionar ou remover itens da coleção de origem, use um loop for.

A postagem no link fornecido por Poko indica que isso é permitido nas novas collections simultâneas.

Faça uma cópia da enumeração, usando um método de extensão IEnumerable, neste caso, e enumerar sobre ele. Isso adicionaria uma cópia de cada elemento em cada enumerável interno a essa enumeração.

 foreach(var item in Enumerable) { foreach(var item2 in item.Enumerable.ToList()) { item.Add(item2) } } 

Como mencionado, mas com um exemplo de código:

 foreach(var item in collection.ToArray()) collection.Add(new Item...); 

Para ilustrar a resposta de Nippysaurus: Se você for adicionar os novos itens à lista e quiser processar os itens recém-adicionados também durante a mesma enumeração, então você pode usar apenas para loop em vez de loop foreach , problema resolvido 🙂

 var list = new List(); ... populate the list ... //foreach (var entryToProcess in list) for (int i = 0; i < list.Count; i++) { var entryToProcess = list[i]; var resultOfProcessing = DoStuffToEntry(entryToProcess); if (... condition ...) list.Add(new YourData(...)); } 

Para exemplo executável:

 void Main() { var list = new List(); for (int i = 0; i < 10; i++) list.Add(i); //foreach (var entry in list) for (int i = 0; i < list.Count; i++) { var entry = list[i]; if (entry % 2 == 0) list.Add(entry + 1); Console.Write(entry + ", "); } Console.Write(list); } 

Saída do último exemplo:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3, 5, 7, 9,

Lista (15 itens)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

Veja como você pode fazer isso (solução rápida e suja. Se você realmente precisa desse tipo de comportamento, você deve reconsiderar seu design ou replace todos os membros IList e agregar a lista de fonts):

 using System; using System.Collections.Generic; namespace ConsoleApplication3 { public class ModifiableList : List { private readonly IList pendingAdditions = new List(); private int activeEnumerators = 0; public ModifiableList(IEnumerable collection) : base(collection) { } public ModifiableList() { } public new void Add(T t) { if(activeEnumerators == 0) base.Add(t); else pendingAdditions.Add(t); } public new IEnumerator GetEnumerator() { ++activeEnumerators; foreach(T t in ((IList)this)) yield return t; --activeEnumerators; AddRange(pendingAdditions); pendingAdditions.Clear(); } } class Program { static void Main(string[] args) { ModifiableList ints = new ModifiableList(new int[] { 2, 4, 6, 8 }); foreach(int i in ints) ints.Add(i * 2); foreach(int i in ints) Console.WriteLine(i * 2); } } } 

LINQ é muito eficaz para fazer malabarismos com collections.

Seus tipos e estrutura não são claros para mim, mas vou tentar encheckboxr seu exemplo com o melhor de minha capacidade.

De seu código, parece que, para cada item, você está adicionando a esse item tudo a partir de sua própria propriedade ‘Enumerable’. Isso é muito simples:

 foreach (var item in Enumerable) { item = item.AddRange(item.Enumerable)); } 

Como um exemplo mais geral, digamos que queremos iterar uma coleção e remover itens onde uma determinada condição é verdadeira. Evitando foreach , usando LINQ:

 myCollection = myCollection.Where(item => item.ShouldBeKept); 

Adicionar um item com base em cada item existente? Sem problemas:

 myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp))); 

Você não pode alterar a coleção enumerável enquanto ela está sendo enumerada, portanto, você precisará fazer suas alterações antes ou depois de enumerar.

O loop for é uma boa alternativa, mas se sua coleção IEnumerable não implementar o ICollection , isso não é possível.

Ou:

1) Copie a coleção primeiro. Enumerar a coleção copiada e alterar a coleção original durante a enumeração. (@tvanfosson)

ou

2) Mantenha uma lista de alterações e confirme após a enumeração.

A melhor abordagem de uma perspectiva de desempenho é provavelmente usar uma ou duas matrizes. Copie a lista para uma matriz, faça operações na matriz e, em seguida, crie uma nova lista na matriz. Acessar um elemento de matriz é mais rápido do que acessar um item de lista, e as conversões entre uma List e um T[] podem usar uma operação rápida de “cópia em massa” que evita a sobrecarga associada ao access de itens individuais.

Por exemplo, suponha que você tenha uma List e deseje ter todas as strings na lista que começam com T sejam seguidas por um item “Boo”, enquanto todas as strings que começam com “U” são descartadas inteiramente. Uma abordagem ideal provavelmente seria algo como:

 int srcPtr,destPtr; string[] arr; srcPtr = theList.Count; arr = new string[srcPtr*2]; theList.CopyTo(arr, theList.Count); // Copy into second half of the array destPtr = 0; for (; srcPtr < arr.Length; srcPtr++) { string st = arr[srcPtr]; char ch = (st ?? "!")[0]; // Get first character of string, or "!" if empty if (ch != 'U') arr[destPtr++] = st; if (ch == 'T') arr[destPtr++] = "Boo"; } if (destPtr > arr.Length/2) // More than half of dest. array is used { theList = new List(arr); // Adds extra elements if (destPtr != arr.Length) theList.RemoveRange(destPtr, arr.Length-destPtr); // Chop to proper length } else { Array.Resize(ref arr, destPtr); theList = new List(arr); // Adds extra elements } 

Teria sido útil se List fornecesse um método para construir uma lista a partir de uma parte de uma matriz, mas não tenho conhecimento de nenhum método eficiente para fazê-lo. Ainda assim, as operações em matrizes são muito rápidas. Digno de nota é o fato de que adicionar e remover itens da lista não requer “empurrar” outros itens; cada item é gravado diretamente em seu local apropriado na matriz.

Você deve realmente usar for() vez de foreach() neste caso.