从C#List <T>中删除项目是否会保留其他项目的订单?

cha*_*ian 11 c# generics collections

最近,我写了很多看起来像这样的代码:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

而我只是变得有点偏执,可能列表在删除时不保留对象顺序.我不清楚C#规范是否足够清楚.有人可以证实我或者不是在问这个模式有问题吗?

编辑:也许我应该澄清,上面是一个非常简单的例子,如果项目需要删除,会发生更多的事情,所以我认为List<T>.RemoveAll()这里不太适用.虽然这是一个很好的功能.我在if()上面的块中添加了一条评论,特别提到了这一点.

dle*_*lev 15

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++;
}
Run Code Online (Sandbox Code Playgroud)

注意,来自阵列拷贝index + 1index; 这是批量转移和"挤压"阵列的项目.但肯定没有对元素进行重新排序.


use*_*016 10

你确实是对的,List<T>.RemoveAt不会改变列表项的顺序.

但是,您的代码段可以简化为使用,List<T>.RemoveAll如下所示:

List<MyObject> myList = new List<MyObject>();
...
myList.RemoveAll(/* Removal predicate */);
Run Code Online (Sandbox Code Playgroud)

编辑以下评论:

myList.Where(/* Removal predicate */).ToList().ForEach(/* Removal code */);
myList.RemoveAll(/* Removal predicate */);
Run Code Online (Sandbox Code Playgroud)

  • 好点子.无需重新发明轮子:) +1 (2认同)

Ahm*_*eed 5

订单应该保持.更好的方法是反向遍历列表:

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

或者你可以使用这个RemoveAll方法:

myList.RemoveAll(item => [item meets removal criteria]);
Run Code Online (Sandbox Code Playgroud)

  • @Kyle反向循环更容易遵循,并且一旦满足条件就不要求索引递减.通过从列表末尾开始并向后工作,如果删除项目,for循环将继续准确.对于`RemoveAll`,它是一个带谓词的单行,并不要求我们循环遍历列表并管理索引. (4认同)

Dan*_*ker 5

虽然接受的答案是对原始问题的一个很好的答案,但Cicada的答案提出了一种替代方法.

使用CLR 4(VS 2010),我们获得了另一种方法,它的另一个优点是每个项目只执行一次谓词(并且可以方便地避免在代码中编写谓词两次).

假设你有一个IEnumerable<string>:

IEnumerable<string> myList = new[] {"apples", "bananas", "pears", "tomatoes"};
Run Code Online (Sandbox Code Playgroud)

您需要根据项目是否通过某些条件将其分为两个列表:

var divided = myList.ToLookup(i => i.Length > 6);
Run Code Online (Sandbox Code Playgroud)

返回的对象有点像Dictionary列表.假设您要保留通过条件的那些:

 myList = divided[true];
Run Code Online (Sandbox Code Playgroud)

您可以使用熟悉的命令循环来操作其他项目:

foreach (var item in divided[false])
    Console.WriteLine("Removed " + item);
Run Code Online (Sandbox Code Playgroud)

请注意,无需List<T>具体使用.我们从不修改现有列表 - 我们只是创建新列表.