Удаление элементов в списке при итерации через него для каждой петли

У меня есть список с именем NeededList Мне нужно проверить каждый элемент в этом списке, чтобы узнать, существует ли он в моей базе данных. Если он существует в базе данных, мне нужно удалить его из списка. Но я не могу изменить список, пока я повторяю его. Как я могу сделать эту работу?

Вот мой код:

For Each Needed In NeededList
        Dim Ticker = Needed.Split("-")(0).Trim()
        Dim Year = Needed.Split("-")(1).Trim()
        Dim Period = Needed.Split("-")(2).Trim()
        Dim Table = Needed.Split("-")(3).Trim()
        Dim dr As OleDbDataReader
        Dim cmd2 As New OleDb.OleDbCommand("SELECT * FROM " & Table & " WHERE Ticker = ? AND [Year] = ? AND Period = ?", con)
        cmd2.Parameters.AddWithValue("?", Ticker)
        cmd2.Parameters.AddWithValue("?", Year)
        cmd2.Parameters.AddWithValue("?", Period)
        dr = cmd2.ExecuteReader
        If dr.HasRows Then
            NeededList.Remove(Needed)
        End If
Next

Ответ 1

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

For x = NeededList.Count - 1 to 0 Step -1
    ' Get the element to evaluate....
    Dim Needed = NeededList(x)
    .....
    If dr.HasRows Then
        NeededList.RemoveAt(x)
    End If
Next

Вам нужно подойти к циклу таким образом, потому что вы не рискуете пропустить элементы, потому что текущий был удален.

Например, предположим, что вы удалите четвертый элемент в коллекции, после этого пятый элемент станет четвертым. Но затем индексатор поднимается до 5. Таким образом, предыдущий пятый элемент (теперь на четвертой позиции) никогда не оценивается. Конечно, вы можете попытаться изменить значение индексатора, но это всегда заканчивается плохим кодом и ошибками, ожидающими появления.

Ответ 2

Идите в безопасное место и сделайте копию с помощью ToList():

For Each Needed In NeededList.ToList()
    Dim Ticker = Needed.Split("-")(0).Trim()
    ...
    If dr.HasRows Then
        NeededList.Remove(Needed)
    End If
Next

Ответ 3

Вы можете использовать цикл For, повторяющийся через каждый индекс с шагом -1.

For i as Integer = NeededList.Count - 1 to 0 Step -1

    Dim Needed = NeededList(i)

    'this is a copy of your code
    Dim Ticker = Needed.Split("-")(0).Trim()
    Dim Year = Needed.Split("-")(1).Trim()
    Dim Period = Needed.Split("-")(2).Trim()
    Dim Table = Needed.Split("-")(3).Trim()

    Dim dr As OleDbDataReader
    Dim cmd2 As New OleDb.OleDbCommand("SELECT * FROM " & Table & " WHERE Ticker = ? AND [Year] = ? AND Period = ?", con)
    cmd2.Parameters.AddWithValue("?", Ticker)
    cmd2.Parameters.AddWithValue("?", Year)
    cmd2.Parameters.AddWithValue("?", Period)
    dr = cmd2.ExecuteReader

    'MODIFIED CODE
    If dr.HasRows Then NeededList.RemoveAt(i)

Next i

Ответ 4

Содержимое массива (или что-либо еще, что вы можете быстро перечислить с помощью For Each) не может быть изменено с помощью цикла For Each. Вам нужно использовать простой цикл For и перебирать каждый индекс.

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

Ответ 5

Нет, вы не можете удалить из списка, над которым вы работаете, например. Для каждой строки как String В listOfStrings                   Если Str.Equals( "Pat" ) Тогда                       Dim index = listOfStrings.IndexOf(Str)                       listOfStrings.RemoveAt(index)                   Конец Если               Далее

Но этот способ будет делать копию вашего списка и удалять из него, например. Для каждой строки как String В listOfStrings                   Если Str.Equals( "Pat" ) Тогда                       Dim index = listOfStringsCopy.IndexOf(Str)                       listOfStringsCopy.RemoveAt(индекс)                   Конец Если               Далее

Ответ 6

Вы также можете инвертировать порядок элементов списка и использовать For Each с помощью расширений IEnumerable Cast и Reverse.

Простой пример с использованием List (Of String):

For Each Needed In NeededList.Cast(Of List(Of String)).Reverse()
    If dr.HasRows Then
        NeededList.Remove(Needed)
    End If
Next

Ответ 7

Как насчет этого (без итерации):

NeededList = (NeededList.Where(Function(Needed) IsNeeded(Needed)).ToList

Function IsNeeded(Needed As ...) As Boolean
    ...
    Return Not dr.HasRows
End Function

Ответ 8

Поскольку списки растут и уменьшаются с самого конца, вы можете решить проблему, выполнив итерацию по списку в обратном порядке.


Теория:

Перевернуть коллекцию быстрее, чем вернуть копию. Поэтому, если вам нужна скорость, используйте list.Reverse(), прежде чем манипулировать коллекцией.


Проверенная производительность:

ReverseToList:   00:00:00.0005484
CopyWithToList:  00:00:00.0017638
CopyWithForeach: 00:00:00.0141009

Реализация:

For Each Needed In NeededList.Reverse()
    Dim Ticker = Needed.Split("-")(0).Trim()
    '...
    If dr.HasRows Then
        NeededList.Remove(Needed)
    End If
Next

Код ниже был использован для проверки производительности методов:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;

public class Program
{
    public static void Main()
    {
        List<int> items = new List<int>();

        items = Enumerable.Range(0, 1000000).ToList();

        Reverse(items);
        CopyWithToList(items);
        CopyWithForeach(items);
    }

    public static void Reverse<T>(List<T> list) 
    {
        var sw = Stopwatch.StartNew();
        list.Reverse();
        sw.Stop();
        Console.WriteLine("ReversedList:    {0}", sw.Elapsed);
    }

    public static void CopyWithToList<T>(List<T> list) 
    {
        var sw = Stopwatch.StartNew();
        List<T> copy = list.ToList();
        sw.Stop();
        Console.WriteLine("CopyWithToList:  {0}", sw.Elapsed);
    }

    public static void CopyWithForeach<T>(List<T> list)
    {
        var sw = Stopwatch.StartNew();
        List<T> copy = new List<T>();
        foreach (T item in list) {
            copy.Add(item);
        }
        sw.Stop();

        Console.WriteLine("CopyWithForeach: {0}", sw.Elapsed);
    }

}