Можете ли вы удалить элемент из списка ‹›, просматривая его в C #

Можете ли вы удалить элемент из списка ‹> во время итерации по нему? Будет ли это работать или есть способ лучше?

Мой код:

foreach (var bullet in bullets)
  {
    if (bullet.Offscreen())
    {
      bullets.Remove(bullet);
    }
  }

-edit- Извините, ребята, это для серебряной игры. Я не понимал, что silverlight отличается от Compact Framework.


person Chris    schedule 09.10.2009    source источник
comment
см. вопрос: stackoverflow.com/questions/308466/   -  person Ray    schedule 17.12.2009


Ответы (7)


Изменить: чтобы уточнить, вопрос касается Silverlight, который, по-видимому, не поддерживает RemoveAll on List`T. Он доступен в полной структуре, CF, XNA версий 2.0+

Вы можете написать лямбду, которая выражает ваши критерии удаления:

bullets.RemoveAll(bullet => bullet.Offscreen());

Или вы можете выбрать те, которые вам нужны, вместо того, чтобы удалять те, которые вам не нужны:

bullets = bullets.Where(b => !b.OffScreen()).ToList();

Или используйте индексатор для перемещения назад по последовательности:

for(int i=bullets.Count-1;i>=0;i--)
{
    if(bullets[i].OffScreen())
    {
        bullets.RemoveAt(i);
    }
}
person Rex M    schedule 09.10.2009
comment
Это очень неэффективно по сравнению с List<T>.RemoveAll (см. Мой ответ). - person Sam Harwell; 09.10.2009
comment
@ 280z28 ах да, про RemoveAll забыл. - person Rex M; 09.10.2009
comment
Мне больше всего подходит ваша вторая версия (обратная итерация). RemoveAll, похоже, больше не работает (устарело?). - person Chris; 09.10.2009
comment
@Chris RemoveAll определенно работает. См. msdn.microsoft.com/en-us/library/wdka673a.aspx - person Rex M; 09.10.2009
comment
@Chris, ладно, он был удален из библиотек Silverlight. Он доступен во всех остальных версиях. - person Rex M; 09.10.2009
comment
@Chris: он не устарел, но также не указан как поддерживаемый для Silverlight. Перейдите по ссылке выше, написанной Rex, и справа вы увидите поддерживаемые версии. Silverlight не включен. Если вы посетите страницу метода List.Count (), вы увидите, что Silverlight поддерживает его. Таким образом, очевидно, что для Silverlight доступны не все методы. - person Ahmad Mageed; 09.10.2009
comment
Ах хорошо. Я видел, что он доступен в формате CF, и подумал, что у Silverlight он будет. Теперь это имеет смысл. - person Chris; 09.10.2009
comment
Я изменил свой ответ, чтобы решить проблему Silverlight, и он значительно более эффективен, чем методы, описанные в этом ответе (удален фактор алгоритмической сложности). - person Sam Harwell; 09.10.2009
comment
Список замены с использованием фильтра Where () - отличное использование самозамены, но не думайте, что вы можете победить этот RemoveAll () - person GONeale; 13.05.2010

bullets.RemoveAll(bullet => bullet.Offscreen());

Изменить: чтобы эта функция работала как есть в Silverlight, добавьте в свой проект следующий метод расширения.

Как и List<T>.RemoveAll, этот алгоритм имеет вид O (N), где N - длина списка, в отличие от O (N * M), где M - количество элементов, удаленных из списка. Поскольку это метод расширения с тем же прототипом, что и метод RemoveAll, найденный во фреймворках, отличных от Silverlight, встроенный будет использоваться, когда он доступен, а этот - бесшовно для сборок Silverlight.

public static class ListExtensions
{
    public static int RemoveAll<T>(this List<T> list, Predicate<T> match)
    {
        if (list == null)
            throw new NullReferenceException();

        if (match == null)
            throw new ArgumentNullException("match");

        int i = 0;
        int j = 0;

        for (i = 0; i < list.Count; i++)
        {
            if (!match(list[i]))
            {
                if (i != j)
                    list[j] = list[i];

                j++;
            }
        }

        int removed = i - j;
        if (removed > 0)
            list.RemoveRange(list.Count - removed, removed);

        return removed;
    }
}
person Sam Harwell    schedule 09.10.2009
comment
Звучит здорово, но removeall не существует для меня как функция (silverlight), даже с использованием system.linq - я не уверен, что не так? - person Chris; 09.10.2009
comment
@Benjol: приятно видеть другие способы добиться чего-то. Иногда имеют смысл другие подходы. Например, обратный шаблон цикла for чрезвычайно полезен с массивами и XML-документами, где метод RemoveAll просто не применяется. Это также полезно в этих сценариях при изменении существующей коллекции. - person Ahmad Mageed; 09.10.2009
comment
Я нашел тему, в которой люди спрашивают, почему removeall был удален из C # в более новых версиях: форумы .silverlight.net / forum / t / 19099.aspx Странно ??? - person Chris; 09.10.2009
comment
ОК, removeall не в серебряном свете. В любом случае это хорошее решение, о котором стоит помнить для других проектов! - person Chris; 09.10.2009
comment
@Chris: исправлено для решения проблемы, и с помощью метода расширения вы можете использовать код как есть, и он будет без проблем поддерживать несколько фреймворков. - person Sam Harwell; 09.10.2009

Попытка удалить его в цикле foreach вызовет исключение. Вам нужно перебрать его в обратном порядке с помощью цикла for.

for (int count = bullets.Count - 1; count >= 0; count--)
{
  if (bullets[count].Offscreen())
    {
        //bullets.Remove(bullets[count]);
        bullets.RemoveAt(count);
    }
}
person Ahmad Mageed    schedule 09.10.2009
comment
Похоже, это хорошая идея, спасибо. Как вы думаете, повторение списка в обратном направлении не приведет к снижению производительности? - person Chris; 09.10.2009
comment
Циклы for обычно считаются быстрыми. Перемещение в обратном направлении не должно сильно отличаться от обычного. Кроме того, нет временного копирования списка, поэтому оно влияет на исходную коллекцию. - person Ahmad Mageed; 09.10.2009
comment
Кроме того, я думаю, что «removeat» будет быстрее, чем «remove». - person Chris; 09.10.2009
comment
@Chris: согласен, это лучший выбор, так как индекс известен. - person Ahmad Mageed; 09.10.2009

Попробуй это:

bullets.RemoveAll(bullet => bullet.Offscreen());
person Magnus Lindhe    schedule 09.10.2009

Лучше либо создать список, который будет содержать элементы для удаления, а затем удалить элементы из списка:

List<Bullet> removedBullets = new List<Bullet>();

foreach(var bullet in bullets)
{
  if (bullet.OffScreen())
  {
   removedBullets.Add(bullet);
  }
}

foreach(var bullet in removedBullets)
{
  bullets.Remove(bullet);
}
person Russell    schedule 09.10.2009
comment
Это было моей первоначальной мыслью, но, поскольку это игра в реальном времени, я не стремлюсь создавать новый список из-за возможных накладных расходов. - person Chris; 09.10.2009

Выполняйте итерацию цикла for вместо того, чтобы повторять цикл foreach. Это сработает.

person Amit    schedule 09.10.2009

Я сталкивался с этой проблемой раньше и писал в блоге об этом здесь.

Краткая версия заключается в том, что вы можете создать метод расширения под названием RemoveIf:

public void RemoveIf<T>(ICollection<T> collection, Predicate<T> match)
{
    List<T> removed = new List<T>();
    foreach (T item in collection)
    {
        if (match(item))
        {
            removed.Add(item); 
        }
    }

    foreach (T item in removed)
    {
        collection.Remove(item);
    }

    removed.Clear();
}

А затем просто вызывайте его со своим делегатом каждый раз, когда вам это нужно:

RemoveIf(_Entities.Item, delegate(Item i) { return i.OffScreen(); });
person Odd    schedule 09.10.2009