2011-08-05 11 views
11

最近、私はこのようなコードをたくさん書いてきた:C#リスト<T>からアイテムを削除すると、他のアイテムの注文は保持されますか?

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 
    } 
} 

をし、私は多分リストは除去でオブジェクトの順序を保持しません少し偏執的になりました。私は確かに知るのに十分にC#仕様を知らない。誰かが私がこのパターンに悩まされているかどうかを確認することができますか?

EDIT:おそらく、私は上記のことが非常に単純化された例であり、アイテムを削除する必要がある場合はもっと多くの事柄が発生するので、ここではList<T>.RemoveAll()がひどく当てはまるとは思わないでしょう。それは素晴らしい機能ですが。上記のブロックif()にコメントを追加しました。

+4

ちょっとした注意:フォワードではなく、リストを逆順にループする必要があります。リスト[1](i = 1)を削除したとします。これにより、リスト[2]の要素がリスト[1]にあるリストがシフトします。今、あなたがi = 2にジャンプすると、リスト[1]にある要素をスキップしました。 –

+1

RemoveAll()メソッドでこのパターンを大幅に簡略化できるはずです –

+0

なぜそれに対してlinqを使用しないのですか? – Andre

答えて

15

List<T>常に追加、挿入、削除するときの相対的な順序を維持する。それがなければリストではないでしょう。

はここRemoveAt()用(ILSpy'ed)コードです:

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に、配列のコピーを注意してください。それはアイテムが卸売りされ、アレイを一緒に「絞る」ようにシフトされています。しかし、要素の並べ替えは間違いありません。リフレクタからの

+0

リストを逆順で実行し、 'RemoveAll()'を検討することについての(非常に参考になる)追加の会話の他に、これは私が実際に尋ねた質問に答えます。ありがとう! – chaosTechnician

3

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

RemoveAtを呼び出すと、削除したインデックスに続くすべての要素がコピーされ、前方に移動します。

降順で並べ替え、その順序で要素を削除します。

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

positionsはインデックスのリストです。 listは削除したいものです(実際のデータが入っています)。

10

あなたは本当に正しいです、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 */); 
+2

良い点。ホイールを再作成する必要はありません:+1 –

+0

これは、各要素が削除されると(削除された要素の直後にある要素をスキップして)リストの移動を防ぎます。私は逆順でリストを移動することをお勧めしますが、彼はずっと優れています! –

+0

元の質問を更新して、単にアイテムを削除する以上のことをしているので、私は 'RemoveAll()'が私のためにそのトリックをするとは思わないと言います。 – chaosTechnician

4

順序が維持されるべきです。より良いアプローチは逆に、リストをトラバースすることです:

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

それともRemoveAll methodを使用することができます。

myList.RemoveAll(item => [item meets removal criteria]); 
+0

これはなぜより良いですか? –

+4

@Kyle reverse forループは従うのが簡単で、基準が満たされた後にインデックスを減らす必要はありません。リストの終わりから始まり、逆方向に作業することで、項目が削除されてもforループは正確になり続けます。 'RemoveAll'に関しては、それは述語を持つ1ライナーなので、リストをループしてインデックスを管理する必要はありません。 –

5

受け入れ答えは、元の質問に素晴らしい答えですが、蝉の答えは選択肢を提案しますアプローチ。

CLR 4(VS 2010)では、項目ごとに1回のみ述語を実行するという利点があります(また、コード内に述語を2回書くのを避けるのが便利です)。

は、あなたが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>を使用する必要がないこと具体的には既存のリストを変更することはありません。新しいリストを作成するだけです。

+0

+1 - これはきれいです!私はそれが存在するのか分からなかった。 –

関連する問題