Сохраняет ли элемент из списка C # <T>заказы других элементов? - PullRequest
11 голосов
/ 05 августа 2011

В последнее время я пишу много кода, который выглядит следующим образом:

List<MyObject> myList = new List<MyObject>();
...
for(int i = 0; i < myList.Count; ++i)
{
  if(/*myList[i] meets removal criteria*/)
  {
     myList.RemoveAt(i);
     --i;  //Check this index again for the next item
     //Do other stuff as well
  }
}

, и я просто стал немного параноиком, что, возможно, List не сохраняет порядок объектов при удалении.Я недостаточно хорошо знаю спецификацию C #, чтобы знать наверняка.Может ли кто-нибудь подтвердить, что я либо не запрашиваю проблемы с этим шаблоном?

РЕДАКТИРОВАТЬ: Возможно, мне следует пояснить, что приведенный выше пример является очень упрощенным, и если что-то нужно удалить, то произойдет больше вещей, поэтомуне думаю, что List<T>.RemoveAll() здесь ужасно применимо.Хотя это хорошая функция.Я добавил комментарий в блоке if() выше, чтобы особо упомянуть это.

Ответы [ 6 ]

15 голосов
/ 05 августа 2011

List<T> будет всегда поддерживать относительный порядок при добавлении, вставке и удалении; это не был бы список, если бы его не было.

Вот (ILSpy'ed) код для RemoveAt():

public void RemoveAt(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    this._size--;
    if (index < this._size)
    {
        Array.Copy(this._items, index + 1, this._items, index, this._size - index);
    }
    this._items[this._size] = default(T);
    this._version++;
}

Обратите внимание на копию массива от index + 1 до index; это предметы, перемещаемые оптом и «сжимающие» массив. Но переопределение элементов определенно не происходит.

10 голосов
/ 05 августа 2011

Вы действительно правы, List<T>.RemoveAt не изменит порядок пунктов списка.

Однако ваш фрагмент может быть упрощен для использования List<T>.RemoveAll следующим образом:

List<MyObject> myList = new List<MyObject>();
...
myList.RemoveAll(/* Removal predicate */);

Редактировать следующий комментарий:

myList.Where(/* Removal predicate */).ToList().ForEach(/* Removal code */);
myList.RemoveAll(/* Removal predicate */);
5 голосов
/ 06 августа 2011

Хотя принятый ответ является отличным ответом на первоначальный вопрос, ответ Цикады предлагает альтернативный подход.

С CLR 4 (VS 2010) мы получаем еще один подход, который имеет дополнительное преимущество, заключающееся в выполнении предиката только один раз для каждого элемента (и делает удобным, чтобы избежать написания предиката дважды в нашем коде).

Предположим, у вас есть IEnumerable<string>:

IEnumerable<string> myList = new[] {"apples", "bananas", "pears", "tomatoes"};

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

var divided = myList.ToLookup(i => i.Length > 6);

Возвращенный объект чем-то напоминает Dictionary списков. Предположим, вы хотите оставить те, которые отвечают критериям:

 myList = divided[true];

И вы можете использовать знакомый императивный цикл для работы с другими элементами:

foreach (var item in divided[false])
    Console.WriteLine("Removed " + item);

Обратите внимание, что нет необходимости использовать List<T> специально. Мы никогда не модифицируем существующий список - мы просто создаем новые.

5 голосов
/ 05 августа 2011

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

for(int i = myList.Count - 1; i >= 0; i--)
{
    if(/*myList[i] meets removal criteria*/)
    {
        myList.RemoveAt(i);
    }
}

Или вы можете использовать метод RemoveAll :

myList.RemoveAll(item => [item meets removal criteria]);
3 голосов
/ 05 августа 2011

От отражателя:

public void RemoveAt(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    this._size--;
    if (index < this._size)
    {
        Array.Copy(this._items, index + 1, this._items, index, this._size - index);
    }
    this._items[this._size] = default(T);
    this._version++;
}

Так что, по крайней мере, с реализацией MS - порядок элементов не меняется на RemoveAt.

2 голосов
/ 05 августа 2011

Когда вы вызываете RemoveAt, все элементы, следующие за индексом, который вы удалите, будут скопированы и сдвинуты вперед.

Сортировка списка позиций по убыванию и удаление элементов в этом порядке.

foreach (var position in positions.OrderByDescending(x=>x))
   list.RemoveAt(position);

positions - список индексов. list это тот, из которого вы хотите удалить (он содержит фактические данные).

...