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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:13:14  来源:igfitidea点击:

Removing Items in a List While Iterating Through It with For Each Loop

vb.netlistloopsforeach

提问by gromit1

I have a list named NeededListI 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 Eachcan not be modified with a For Eachloop. You need to use a simple Forloop 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 Eachusing the IEnumerable Castand Reverseextensions.

您也可以反转列表中的元素的顺序,并仍然使用For Each使用IEnumerable CastReverse扩展。

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