web-dev-qa-db-ja.com

「foreach」ループでリストを変更する最良の方法は何ですか?

C#/ .NET 4.0の新機能は、例外を取得せずにforeachの列挙型を変更できることです。この変更の詳細については、Paul Jacksonのブログエントリ並行処理の興味深い副作用:列挙中のコレクションからのアイテムの削除を参照してください。 。

以下を行う最良の方法は何ですか?

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable)
    {
        item.Add(new item2)
    }
}

通常、IListの最後までキャッシュ/バッファとしてforeachを使用しますが、もっと良い方法はありますか?

66
Polo

Foreachで使用されるコレクションは不変です。これは非常に設計によるものです。

[〜#〜] msdn [〜#〜]

Foreachステートメントは、コレクションを反復して必要な情報を取得するために使用されますが、予期しない副作用を避けるためにソースコレクションにアイテムを追加または削除するために使用することはできません。 ソースコレクションからアイテムを追加または削除する必要がある場合は、forループを使用します。

Pokoが提供する link の投稿は、これが新しい同時コレクションで許可されていることを示しています。

67
Rik

この場合、IEnumerable拡張メソッドを使用して列挙のコピーを作成し、列挙します。これは、その列挙に列挙可能なすべての内部のすべての要素のコピーを追加します。

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}
15
tvanfosson

前述のとおりですが、コードサンプルがあります。

foreach(var item in collection.ToArray())
    collection.Add(new Item...);
8
eulerfx

ニッピサウルスの答えを説明するには:addをリストに追加し、同じ列挙中に新しく追加されたアイテムも処理する場合は、forを使用します。 foreachループの代わりにループ、問題解決:)

var list = new List<YourData>();
... populate the list ...

//foreach (var entryToProcess in list)
for (int i = 0; i < list.Count; i++)
{
    var entryToProcess = list[i];

    var resultOfProcessing = DoStuffToEntry(entryToProcess);

    if (... condition ...)
        list.Add(new YourData(...));
}

実行可能な例:

void Main()
{
    var list = new List<int>();
    for (int i = 0; i < 10; i++)
        list.Add(i);

    //foreach (var entry in list)
    for (int i = 0; i < list.Count; i++)
    {
        var entry = list[i];
        if (entry % 2 == 0)
            list.Add(entry + 1);

        Console.Write(entry + ", ");
    }

    Console.Write(list);
}

最後の例の出力:

0、1、2、3、4、5、6、7、8、9、1、3、5、7、9、9

リスト(15アイテム)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

7
Roland Pihlakas

これを行う方法は次のとおりです(迅速で汚れたソリューション。本当にこのような動作が必要な場合は、設計を再検討するか、すべてのIList<T>メンバーをオーバーライドしてソースリストを集約する必要があります)。

using System;
using System.Collections.Generic;

namespace ConsoleApplication3
{
    public class ModifiableList<T> : List<T>
    {
        private readonly IList<T> pendingAdditions = new List<T>();
        private int activeEnumerators = 0;

        public ModifiableList(IEnumerable<T> collection) : base(collection)
        {
        }

        public ModifiableList()
        {
        }

        public new void Add(T t)
        {
            if(activeEnumerators == 0)
                base.Add(t);
            else
                pendingAdditions.Add(t);
        }

        public new IEnumerator<T> GetEnumerator()
        {
            ++activeEnumerators;

            foreach(T t in ((IList<T>)this))
                yield return t;

            --activeEnumerators;

            AddRange(pendingAdditions);
            pendingAdditions.Clear();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ModifiableList<int> ints = new ModifiableList<int>(new int[] { 2, 4, 6, 8 });

            foreach(int i in ints)
                ints.Add(i * 2);

            foreach(int i in ints)
                Console.WriteLine(i * 2);
        }
    }
}
4
Anton Gogolev

[〜#〜] linq [〜#〜] は、コレクションのジャグリングに非常に効果的です。

あなたのタイプと構造は私には不明確ですが、私はあなたの例を私の能力の最大に合わせようとします。

コードから、各アイテムについて、独自の「Enumerable」プロパティからすべてをそのアイテムに追加しているように見えます。これは非常に簡単です。

foreach (var item in Enumerable)
{
    item = item.AddRange(item.Enumerable));
}

より一般的な例として、コレクションを反復処理し、特定の条件が満たされているアイテムを削除するとします。 LINQを使用したforeachの回避:

myCollection = myCollection.Where(item => item.ShouldBeKept);

既存の各アイテムに基づいてアイテムを追加しますか?問題ない:

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));
2
Timo

列挙中は列挙可能なコレクションを変更できないため、列挙の前または後に変更を行う必要があります。

forループは素晴らしい代替手段ですが、IEnumerableコレクションがICollectionを実装していない場合は不可能です。

どちらか:

1)最初にコレクションをコピーします。コピーされたコレクションを列挙し、列挙中に元のコレクションを変更します。 (@tvanfosson)

または

2)変更のリストを保持し、列挙後にそれらをコミットします。

1
Josh G

パフォーマンスの観点からの最善のアプローチは、おそらく1つまたは2つのアレイを使用することです。リストを配列にコピーし、配列に対して操作を行ってから、配列から新しいリストを作成します。配列要素へのアクセスは、リストアイテムへのアクセスよりも高速であり、List<T>T[]の間の変換では、個々のアイテムへのアクセスに関連するオーバーヘッドを回避する高速「一括コピー」操作を使用できます。

たとえば、List<string>があり、リスト内のTで始まるすべての文字列の後に項目「Boo」が続き、「U」で始まるすべての文字列を削除するとします。完全に。最適なアプローチは、おそらく次のようなものです。

int srcPtr,destPtr;
string[] arr;

srcPtr = theList.Count;
arr = new string[srcPtr*2];
theList.CopyTo(arr, theList.Count); // Copy into second half of the array
destPtr = 0;
for (; srcPtr < arr.Length; srcPtr++)
{
  string st = arr[srcPtr];
  char ch = (st ?? "!")[0]; // Get first character of string, or "!" if empty
  if (ch != 'U')
    arr[destPtr++] = st;
  if (ch == 'T')
    arr[destPtr++] = "Boo";
}
if (destPtr > arr.Length/2) // More than half of dest. array is used
{
  theList = new List<String>(arr); // Adds extra elements
  if (destPtr != arr.Length)
    theList.RemoveRange(destPtr, arr.Length-destPtr); // Chop to proper length
}
else
{
  Array.Resize(ref arr, destPtr);
  theList = new List<String>(arr); // Adds extra elements
}

List<T>が配列の一部からリストを作成するメソッドを提供してくれたら助かりましたが、そうするための効率的な方法は知りません。それでも、配列の操作は非常に高速です。注目すべきは、リストにアイテムを追加および削除する際に、他のアイテムを「プッシュ」する必要がないという事実です。各アイテムは、配列内の適切な場所に直接書き込まれます。

1
supercat

Timoの答えに追加するには、LINQも次のように使用できます。

items = items.Select(i => {

     ...
     //perform some logic adding / updating.

     return i / return new Item();
     ...

     //To remove an item simply have logic to return null.

     //Then attach the Where to filter out nulls

     return null;
     ...


}).Where(i => i != null);
0
DDiVita

この場合、for()の代わりにforeach()を実際に使用する必要があります。

0
Nippysaurus

簡単な手順を1つ書いたが、このためパフォーマンスが低下する

ここに私のコードスニペットがあります:-

for (int tempReg = 0; tempReg < reg.Matches(lines).Count; tempReg++)
                            {
                                foreach (Match match in reg.Matches(lines))
                                {
                                    var aStringBuilder = new StringBuilder(lines);
                                    aStringBuilder.Insert(startIndex, match.ToString().Replace(",", " ");
                                    lines[k] = aStringBuilder.ToString();
                                    tempReg = 0;
                                    break;
                                }
                            }
0
pravin ghare