Мне нужно удалить несколько строк из таблицы данных. Я слышал, что нельзя менять коллекцию во время ее повторения. Таким образом, вместо цикла for, в котором я проверяю, соответствует ли строка требованиям для удаления, а затем помечаю ее как удаленную, я должен сначала выполнить итерацию по таблице данных и добавить все строки в список, затем выполнить итерацию по списку и отметить строки для удалений. Каковы причины этого и какие у меня есть альтернативы (вместо использования списка строк, которые я имею в виду)?
Как изменить или удалить элементы из перечисляемой коллекции во время итерации по ней в C#
Ответы (8)
Вы можете удалить элементы из коллекции, если используете простой цикл for.
Взгляните на этот пример:
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);
}
Итерация в обратном направлении по списку звучит как лучший подход, потому что если вы удаляете элемент, а другие элементы «попадают в пробел», это не имеет значения, потому что вы уже просмотрели их. Кроме того, вам не нужно беспокоиться о том, что ваша переменная-счетчик станет больше, чем .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);
}
}
Взяв код @bruno, я бы сделал это в обратном порядке.
Потому что, когда вы двигаетесь назад, отсутствующие индексы массива не мешают порядку вашего цикла.
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);
}
А если серьезно, в наши дни я бы использовал LINQ:
var l = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6 });
l.RemoveAll(n => n % 2 == 0);
Поскольку вы работаете с DataTable и должны иметь возможность сохранять любые изменения обратно на сервер с помощью адаптера таблицы (см. комментарии), вот пример того, как вы должны удалять строки:
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();
}
}
Вызов удаления для строк изменит их свойство RowState на Deleted, но оставит удаленные строки в таблице. Если вам все еще нужно работать с этой таблицей, прежде чем сохранять изменения обратно на сервер (например, если вы хотите отобразить содержимое таблицы за вычетом удаленных строк), вам нужно проверить RowState каждой строки, когда вы повторяете ее, как это :
foreach (DataRow row in dt.Rows)
{
if (row.RowState != DataRowState.Deleted)
{
// this row has not been deleted - go ahead and show it
}
}
Удаление строк из коллекции (как в ответе Бруно) сломает адаптер таблицы и, как правило, не должно выполняться с DataTable.
Цикл while справится с этим:
int i = 0;
while(i < list.Count)
{
if(<codition for removing element met>)
{
list.RemoveAt(i);
}
else
{
i++;
}
}
Решение chakrit также можно использовать, если вы нацелены на .NET 2.0 (без выражений LINQ/лямбда), используя делегат, а не лямбда-выражение:
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));
}
Как вы сказали, удаление или добавление в список во время его повторения может сломать его.
Я часто использовал подход с двумя списками для решения проблемы:
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.
Когда мне нужно удалить элемент из перечисляемой коллекции, я обычно перечисляю его в обратном порядке.