vb.net 使用 For Each 循环遍历列表时删除列表中的项目
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19252285/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Removing Items in a List While Iterating Through It with For Each Loop
提问by gromit1
I have a list named NeededList
I need to check each item in this list to see if it exists in my database. If it does exist in the database I need to remove it from the list. But I can't change the list while I'm iterating through it. How can I make this work?
我有一个名为NeededList
我需要检查此列表中的每个项目以查看它是否存在于我的数据库中的列表。如果它确实存在于数据库中,我需要将其从列表中删除。但是在遍历列表时我无法更改列表。我怎样才能使这项工作?
Here is my code so far:
到目前为止,这是我的代码:
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
回答by Steve
No you can't do that using a for each, but you can do that using the old fashioned for .. loop.
The trick is to start from the end and looping backwards.
不,您不能使用 for each 来做到这一点,但是您可以使用老式的 for .. 循环来做到这一点。
诀窍是从结尾开始向后循环。
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
You need to approach the loop in this way because you don't risk to skip elements because the current one has been deleted.
您需要以这种方式处理循环,因为您不会冒跳过元素的风险,因为当前元素已被删除。
For example, suppose that you remove the fourth element in the collection, after that, the fifth element becomes the fourth. But then the indexer goes up to 5. In this way, the previous fifth element (now in fourth position) is never evaluated. Of course you could try to change the value of the indexer but this ends always in bad code and bugs waiting to happen.
例如,假设您删除集合中的第四个元素,之后第五个元素成为第四个。但随后索引器上升到 5。这样,前五个元素(现在在第四个位置)永远不会被评估。当然,您可以尝试更改索引器的值,但这总是以错误的代码和等待发生的错误结束。
回答by Henk Holterman
Go for safe and make a copy with ToList()
:
为安全起见并使用以下内容制作副本ToList()
:
For Each Needed In NeededList.ToList()
Dim Ticker = Needed.Split("-")(0).Trim()
...
If dr.HasRows Then
NeededList.Remove(Needed)
End If
Next
回答by tezzo
You can use a For loop iterating through every index with Step -1.
您可以使用 For 循环通过 Step -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
回答by nhgrif
The contents of an array (or anything else you can fast enumerate with For Each
can not be modified with a For Each
loop. You need to use a simple For
loop and iterate through every index.
数组的内容(或任何您可以快速枚举的内容For Each
不能用For Each
循环修改。您需要使用一个简单的For
循环并遍历每个索引。
Hint: Because you'll be deleting indexes, I suggest starting at the last index and work your way toward the first index so you don't skip over one every time you delete one.
提示:因为您将要删除索引,所以我建议从最后一个索引开始,然后朝着第一个索引前进,这样每次删除一个索引时就不会跳过一个。
回答by Nick4814
Since lists grow and shrink from their end, you can solve the problem by iterating over the list in reverseorder.
由于列表从末尾开始增长和缩小,您可以通过以相反的顺序迭代列表来解决问题。
Theory:
理论:
Reversing the collection is quicker than returning a copy. So if you need speed, use list.Reverse() before manipulating the collection.
反转集合比返回副本更快。因此,如果您需要速度,请在操作集合之前使用 list.Reverse()。
Tested performance:
测试性能:
ReverseToList: 00:00:00.0005484
CopyWithToList: 00:00:00.0017638
CopyWithForeach: 00:00:00.0141009
Implementation:
执行:
For Each Needed In NeededList.Reverse()
Dim Ticker = Needed.Split("-")(0).Trim()
'...
If dr.HasRows Then
NeededList.Remove(Needed)
End If
Next
Code below was used to test the performance of the methods:
下面的代码用于测试这些方法的性能:
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);
}
}
回答by Pec1983
No you can not remove from a List that you are working on e.g. For Each Str As String In listOfStrings If Str.Equals("Pat") Then Dim index = listOfStrings.IndexOf(Str) listOfStrings .RemoveAt(index) End If Next
不,您不能从您正在处理的列表中删除,例如 For Each Str As String In listOfStrings If Str.Equals("Pat") Then Dim index = listOfStrings.IndexOf(Str) listOfStrings .RemoveAt(index) End If Next
But this way will work make a copy of your list and delete from it e.g. For Each Str As String In listOfStrings If Str.Equals("Pat") Then Dim index = listOfStringsCopy.IndexOf(Str) listOfStringsCopy.RemoveAt(index) End If Next
但是这种方式可以复制您的列表并从中删除,例如 For Each Str As String In listOfStrings If Str.Equals("Pat") Then Dim index = listOfStringsCopy.IndexOf(Str) listOfStringsCopy.RemoveAt(index) End If下一个
回答by mikro
You can also invert the order of the list's elements and still use For Each
using the IEnumerable Cast
and Reverse
extensions.
您也可以反转列表中的元素的顺序,并仍然使用For Each
使用IEnumerable Cast
和Reverse
扩展。
Simple example using a List(Of String):
使用 List(Of String) 的简单示例:
For Each Needed In NeededList.Cast(Of List(Of String)).Reverse()
If dr.HasRows Then
NeededList.Remove(Needed)
End If
Next
回答by C66
How about this (no iteration needed):
这个怎么样(不需要迭代):
NeededList = (NeededList.Where(Function(Needed) IsNeeded(Needed)).ToList
Function IsNeeded(Needed As ...) As Boolean
...
Return Not dr.HasRows
End Function