在 ForEach 循环中安全地移除 DataRow

43

我不明白为什么这段代码不起作用。

foreach (DataRow dataRow in dataTable.Rows)
{
    if (true)
    {
        dataRow.Delete();
    }
}

对我来说似乎可以工作。不过,我猜dataTable.Rows.Remove(dataRow);不会起作用。 - synergetic
15个回答

63

最安全的方法-使用 for 循环

for (int i = datatable.Rows.Count - 1; i >= 0; i--) 
{
    if (true)
    {
        datatable.Rows[i].Delete();
    }
}

别忘了使用AcceptChanges方法来移除所有标记的行:

datatable.AcceptChanges();

1
不错。使用“datatable.AcceptChanges()”来真正删除行。否则,它们仍然在数据表中 - 其行状态为“已删除”。 - peter
为什么需要“if(true)”? - Manan Shah
2
if (true) 是一个占位符,用于检查是否需要删除该行。否则,您的数据表中的所有行都将被删除。 - VMAtm

36
尽管 DataRow.Delete 不会修改集合的状态,但微软文档指出,在迭代 DataRowCollection 对象时不应调用 Delete 或 Remove 方法:

在迭代 DataRowCollection 对象时,不应调用 Delete 或 Remove 方法。Delete 和 Remove 方法都会修改集合的状态。

通常最好的解决方法是创建另一个集合(例如 List<DataRow>)来存储要删除的元素,然后在迭代完成后再删除它们。

大多数 .NET 中的集合都不允许在遍历时改变集合中的内容,因此这也适用于从集合中删除元素的情况。


不是母语者,但第二个“nor”似乎没有意义。那是什么意思? - Cee McSharpface
2
@CeeMcSharpface:它的意思是“Delete不会修改集合的状态,而Remove会修改集合的状态。” - Jon Skeet
谢谢。明白了。因此,建议不要这样做,因为在foreach循环变量上调用看起来可能会修改可枚举对象的方法是一种普遍存在的不良模式,而不是因为在特定情况下会有害。 - Cee McSharpface
@CeeMcSharpface:老实说,11年后我已经记不清细节了... - Jon Skeet
@Moses 我曾经在NET 4.5上这样做。升级到NET 4.8让我头疼,显然是因为我这样做(通过foreach迭代,执行DataRow.Delete(),最后调用DataTable.AcceptChanges())。我认为这种方法过去曾经有效,但现在我们真的应该改变它。 - Daniel Wu
显示剩余3条评论

21

使用 foreach 语句迭代集合时,您不能修改该集合。

您可以尝试类似以下的操作:

List<DataRow> deletedRows = new List<DataRow>();

foreach (DataRow dataRow in dataTable.Rows)
{
    if(true) deletedRows.Add(dataRow);
}

foreach(DataRow dataRow in deletedRows)
{
    dataRow.Delete();
}

7
错误。您不能在使用“foreach”循环遍历集合时修改它。您可以使用标准的“for”循环。例如:for(int i = datatable.Rows.length - 1; i >= 0; i --) { //remove rows } - AllenG
在迭代过程中可以进行删除操作。请参考以下链接:https://dev59.com/jXA75IYBdhLWcg3ws7Yh - Developer
@fzshah76:不,根据你的例子,使用Select()方法会创建一个包含先前集合中所有行的新集合。你没有在包含要删除的行的原始集合上进行迭代。 - Thibault Falise
不错的解决方案,没有过多的hackish,运行得很好。但是由于跨越这个额外的小障碍非常容易,这让我想知道为什么微软不直接在迭代中本地支持这个功能,即使在幕后他们也为我们跨越了这个障碍。 - HerrimanCoder

9

5

我的回答可能已经不再有用了。在.NET 2.0及更早版本中,只有在使用DataRow时才会抛出异常。原因请参考msdn:http://msdn.microsoft.com/en-us/library/system.data.datarow.delete(v=vs.80).aspx

如果行的RowState为Added,则该行将从表中删除。

在调用Delete方法后,RowState变为Deleted。它保持为Deleted状态直到您调用AcceptChanges方法。

可以通过调用RejectChanges来取消删除的行。

为了解决这个问题,您可以在使用foreach之前调用DataTable.AcceptChanges()。


3
foreach (DataRow dataRow in dataTable.Rows)
{
    if (true)
    {
        dataRow.Delete();
    }
}

dataTable.AcceptChanges();

请参考下面的截图以了解其工作原理。
1. 仅从 DataTable 中删除,而不是彻底删除。 enter image description here 2. 在 AcceptChanges() 函数执行之前打断点。 enter image description here 3. 执行 AcceptChanges() 函数后。 enter image description here 希望这个问题现在已经解决了。

2
我同意你的答案,但是你如何解释这个问题:http://msdn.microsoft.com/en-us/library/system.data.datarow.delete.aspx "在遍历DataRowCollection对象时不应该在foreach循环中调用Delete方法。Delete会修改集合的状态。" MSDN文档中的错误? - Eternal21

3
这是因为这就像试图拆卸你爬的楼梯一样,简单来说,你不能删除正在迭代的项。因此,您应该使用不同的数组来迭代,并从datatable的Rows属性中删除它们。
// Select() method returns an array and you can iterate through while remove
foreach (DataRow row in dataTable.Select())
{
    if (true)
    {
        dataTable.Rows.Remove(row);
    }
}

2
使用列表将需要删除的行映射,然后在DataTable迭代之外删除行是最简单的实现方式。
C#
    List<DataRow> rowsWantToDelete= new List<DataRow>();

    foreach (DataRow dr in dt.Rows)
    {
        if(/*Your condition*/)
        {
            rowsWantToDelete.Add(dr);
        }
    }

    foreach(DataRow dr in rowsWantToDelete)
    {
        dt.Rows.Remove(dr);
    }

VB

Dim rowsWantToDelete As New List(Of DataRow)

For Each dr As DataRow In dt
    If 'Your condition' Then
        rowsWantToDelete .Add(dr)
    End If
Next

For Each dr As DataRow In rowsWantToDelete 
    dt.Rows.Remove(dr)
Next

2

Rows内容在您进行迭代时会发生更改,如果删除一行,则迭代将无效。

但是,您可以先将行复制到集合中,然后再迭代该集合并以此方式删除行。这确保了迭代不会被更改的数据中断。


2

这里有另一个版本(我认为更容易理解)我刚刚使用了:

int i=0;
while (i < myDataTable.Rows.Count)
{
    if (condition)  //should it be deleted?
        myDataTable.Rows.RemoveAt(i);
    else
        i++;
}

更快速。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接