Удаление элементов в списке при итерации через него для каждой петли
У меня есть список с именем 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);
}
}