while - remove list c# foreach




Como remover elementos de uma lista genérica enquanto iterar sobre ele? (15)

Eu estou procurando um padrão melhor para trabalhar com uma lista de elementos que precisam ser processados ​​e, em seguida, dependendo do resultado são removidos da lista.

Você não pode usar .Remove(element) dentro de um foreach (var element in X) (porque resulta em Collection was modified; enumeration operation may not execute. Exceção) ... você também não pode usar for (int i = 0; i < elements.Count(); i++) e .RemoveAt(i) porque interrompe sua posição atual na coleção em relação a i .

Existe uma maneira elegante de fazer isso?


A iteração reversa deve ser a primeira coisa que vem à mente quando você quiser remover elementos de uma Coleção enquanto iterar sobre ela.

Felizmente, existe uma solução mais elegante do que escrever um loop for que envolve digitação desnecessária e pode ser propenso a erros.

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}

A melhor maneira de remover itens de uma lista durante a iteração é usar RemoveAll() . Mas a principal preocupação escrita pelas pessoas é que elas têm que fazer algumas coisas complexas dentro do loop e / ou ter casos de comparação complexos.

A solução é ainda usar RemoveAll() mas use esta notação:

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});

Como qualquer remoção é feita com uma condição que você pode usar

list.RemoveAll(item => item.Value == someValue);

Copie a lista que você está iterando. Em seguida, remova da cópia e coloque o original. Retroceder é confuso e não funciona bem ao fazer um loop paralelo.

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});

Eu me encontrei em uma situação semelhante, onde eu tinha que remover todo elemento n em uma determinada List<T> .

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}

Eu reatribuiria a lista de uma consulta LINQ que filtrava os elementos que você não queria manter.

list = list.Where(item => ...).ToList();

A menos que a lista seja muito grande, não deve haver problemas significativos de desempenho ao fazer isso.


Minha abordagem é criar primeiro uma lista de índices, que devem ser excluídos. Depois, faço um loop pelos índices e removo os itens da lista inicial. Isso parece com isso:

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}

O custo de remover um item da lista é proporcional ao número de itens após o item a ser removido. No caso em que a primeira metade dos itens se qualifique para remoção, qualquer abordagem baseada na remoção de itens individualmente acabará tendo que executar operações de cópia de itens N * N / 4, que podem ficar muito caras se a lista for grande .

Uma abordagem mais rápida é examinar a lista para encontrar o primeiro item a ser removido (se houver) e, a partir desse ponto, copiar cada item que deve ser retido no local a que pertence. Feito isso, se os itens R devem ser mantidos, os primeiros R itens na lista serão aqueles itens R, e todos os itens que requerem exclusão estarão no final. Se esses itens forem apagados na ordem inversa, o sistema não terá que copiar nenhum deles, portanto, se a lista tiver N itens dos quais R itens, incluindo todos os primeiros F, forem mantidos, será necessário copiar itens de RF e encolher a lista por um item NR vezes. Todo o tempo linear.


Selecione os elementos que você deseja, em vez de tentar remover os elementos que você não deseja. Isso é muito mais fácil (e geralmente mais eficiente também) do que remover elementos.

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

Eu queria postar isso como um comentário em resposta ao comentário deixado por Michael Dillon abaixo, mas é muito longo e provavelmente útil ter minha resposta de qualquer maneira:

Pessoalmente, eu nunca removeria itens um-por-um, se você precisar de remoção, então chame RemoveAll que usa um predicado e apenas rearranja o array interno uma vez, enquanto Remove faz uma operação Array.Copy para cada elemento que você remove. RemoveAll é muito mais eficiente.

E quando você está invertendo sobre uma lista, você já tem o índice do elemento que deseja remover, então seria muito mais eficiente chamar RemoveAt , porque Remove primeiro faz um percurso da lista para encontrar o índice do elemento. elemento que você está tentando remover, mas você já conhece esse índice.

Portanto, apesar de tudo, não vejo nenhum motivo para chamar o Remove em um for-loop. E, idealmente, se for possível, use o código acima para transmitir elementos da lista conforme necessário, para que não seja criada nenhuma segunda estrutura de dados.


Uma solução simples e direta:

Use um loop padrão para execução em sua coleção e RemoveAt(i) para remover elementos.


Usando o ToArray () em uma lista genérica permite que você faça um Remove (item) na sua lista genérica:

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }

Usar Remove ou RemoveAt em uma lista enquanto iterar sobre essa lista foi intencionalmente dificultado, porque quase sempre é a coisa errada a se fazer . Você pode ser capaz de fazê-lo funcionar com algum truque inteligente, mas seria extremamente lento. Toda vez que você chama Remove ele precisa varrer toda a lista para encontrar o elemento que deseja remover. Toda vez que você chama RemoveAt ele precisa mover a posição 1 dos elementos subseqüentes para a esquerda. Como tal, qualquer solução usando Remove ou RemoveAt , requereria tempo quadrático, O (n²) .

Use RemoveAll se puder. Caso contrário, o seguinte padrão filtrará a lista no local em tempo linear, O (n) .

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);

foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

Basta criar uma lista inteiramente nova a partir da primeira. Eu digo "Fácil" ao invés de "Direito", pois criar uma lista inteiramente nova provavelmente tem um desempenho superior ao método anterior (eu não me incomodei em fazer benchmarking). Eu geralmente prefiro esse padrão, ele também pode ser útil na superação. Limitações de Linq-To-Entities.

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

Desta forma, percorre a lista de trás para a frente com um loop For plain antigo. Fazer isso para a frente pode ser problemático se o tamanho da coleção mudar, mas o inverso deve ser sempre seguro.


 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

Se você adicionar ".ToList ()" à sua lista (ou os resultados de uma consulta LINQ), você pode remover "item" diretamente de "lista" sem o temido "A coleta foi modificada; a operação de enumeração pode não ser executada ." erro. O compilador faz uma cópia da "lista", para que você possa fazer a remoção com segurança na matriz.

Embora esse padrão não seja super eficiente, ele tem uma sensação natural e é flexível o suficiente para quase todas as situações . Por exemplo, quando você deseja salvar cada "item" em um banco de dados e removê-lo da lista somente quando a proteção do banco de dados é bem-sucedida.


List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));




key-value