views:

4982

answers:

8

I have to delete some rows from a data table. I've heard that it is not ok to change a collection while iterating through it. So instead of a for loop in which I check if a row meets the demands for deletion and then mark it as deleted, I should first iterate through the data table and add all of the rows in a list, then iterate through the list and mark the rows for deletions. What are the reasons for this, and what alternatives do I have (instead of using the rows list I mean)?.

+8  A: 

You can remove elements from a collection if you use a simple for loop.

Take a look at this example:

        var l = new List<int>();

        l.Add(0);
        l.Add(1);
        l.Add(2);
        l.Add(3);
        l.Add(4);
        l.Add(5);
        l.Add(6);

        for (int i = 0; i < l.Count; i++)
        {
            if (l[i] % 2 == 0)
            {
                l.RemoveAt(i);
                i--;
            }
        }

        foreach (var i in l)
        {
            Console.WriteLine(i);
        }
bruno conde
This is flawed as not all elements will be checked. If you remove an element at i then then element at i + 1 becomes i. When i is then incremented for the next loop it will skip the element that has just replaced the removed element (hope that makes sense).
Andy Rose
I corrected this. Thanks Andy for the slap. But my point was that you can change a collection with a for loop.
bruno conde
Can I use l[i].Delete(), since removeat will create problems with the table adapter on the update procedure (the rows will be removed from the table instead of just being marked as deleted).
iulianchira
If you just want to mark these objects as "Deleted", you can use a foreach loop because you don't actually remove any objects and the collection remains the same...
bruno conde
@Bruno - Absolutelly agree a for loop can be used to edit a collection and your edit has fixed the element skipping issue.
Andy Rose
@iulianchira: you are correct that you don't want to use RemoveAt with rows in a DataTable as this will break the table adapter. See my new answer below.
MusiGenesis
Wouldn't it be better to iterate *backwards*? for(int i = l.count; i > 0; i--). That way if you remove an element and the next element "falls in it's place", it does not matter because you already checked that. Or am I missing something here?
Michael Stum
@Michael - No your correct. Your solution is more efficient. +1
bruno conde
It would be better to use the built-in RemoveAll method, which takes a predicate as a parameter.
ChrisW
A: 

A while loop would handle this:

int i = 0;
while(i < list.Count)
{
    if(<codition for removing element met>)
    {
        list.RemoveAt(i);
    }
    else
    {
        i++;
    }
}
Andy Rose
This solution faces the same problem as above, your indexes will be off if you delete an item.
Element
No they won't as the index is only incremented when the element is not removed.
Andy Rose
+1  A: 

Deleting or adding to the list whilst iterating through it can break it, like you said.

I often used a two list approach to solve the problem:

ArrayList matches = new ArrayList();   //second list

for MyObject obj in my_list
{

    if (obj.property == value_i_care_about)
        matches.addLast(obj);
}

//now modify

for MyObject m in matches
{
    my_list.remove(m); //use second list to delete from first list
}

//finished.
Philluminati
+4  A: 

Since you're working with a DataTable and need to be able to persist any changes back to the server with a table adapter (see comments), here is an example of how you should delete rows:

DataTable dt;
// remove all rows where the last name starts with "B"
foreach (DataRow row in dt.Rows)
{
    if (row["LASTNAME"].ToString().StartsWith("B"))
    {
        // mark the row for deletion:
        row.Delete();
    }
}

Calling delete on the rows will change their RowState property to Deleted, but leave the deleted rows in the table. If you still need to work with this table before persisting changes back to the server (like if you want to display the table's contents minus the deleted rows), you need to check the RowState of each row as you're iterating through it like this:

foreach (DataRow row in dt.Rows)
{
    if (row.RowState != DataRowState.Deleted)
    {
        // this row has not been deleted - go ahead and show it
    }
}

Removing rows from the collection (as in bruno's answer) will break the table adapter, and should generally not be done with a DataTable.

MusiGenesis
+23  A: 

Iterating Backwards through the List sounds like a better approach, because if you remove an element and other elements "fall into the gap", that does not matter because you have already looked at those. Also, you do not have to worry about your counter variable becoming larger than the .Count.

        List<int> test = new List<int>();
        test.Add(1);
        test.Add(2);
        test.Add(3);
        test.Add(4);
        test.Add(5);
        test.Add(6);
        test.Add(7);
        test.Add(8);
        for (int i = test.Count-1; i > -1; i--)
        {
            if(someCondition){
                test.RemoveAt(i);
            }
        }
Michael Stum
A: 

When I need to remove an item from a collection that I am enumerating I usually enumerate it in reverse.

Adrian
+9  A: 

Taking @bruno code, I'd do it backwards.

Because when you move backwards, the missing array indices do not interfere with the order of your loop.

var l = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6 });

for (int i = l.Count - 1; i >= 0; i--)
    if (l[i] % 2 == 0)
        l.RemoveAt(i);

foreach (var i in l)
{
    Console.WriteLine(i);
}

But seriuosly, these days, I'd use LINQ:

var l = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6 });

l.RemoveAll(n => n % 2 == 0);
chakrit
+1 for realising the power of LINQ for something like this. Why do things the long way when you don't have to
BenAlabaster
why not defining the list l as List<int> instead of var if you know it´s gonna be a List<int>?+1 though
sebastian
+ 1 for RemoveAll. Note RemoveAll is not strictly LINQ. It is available for any 'List' class in .NET 2.0 without LINQ support.
Ray Vega
@sebastion I think you misunderstand how "var" works.
chakrit
+1  A: 

chakrit's solution can also be used if you are targetting .NET 2.0 (no LINQ/lambda expressions) by using a delegate rather than a lambda expression:

public bool IsMatch(int item) {
    return (item % 3 == 1); // put whatever condition you want here
}
public void RemoveMatching() {
    List<int> x = new List<int>();
    x.RemoveAll(new Predicate<int>(IsMatch));
}