96

foreachC# / .NET 4.0 の新機能は、例外を取得せずに列挙型を変更できることです。この変更の詳細については、Paul Jackson のブログ エントリAn Interesting Side-Effect of Concurrency: Removal from a Collection While Enumratingを参照してください。

次のことを行う最善の方法は何ですか?

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

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

4

11 に答える 11

86

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

MSDNで言うように:

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

Poko が提供するリンクの投稿は、これが新しい同時収集で許可されていることを示しています。

于 2009-04-17T10:56:17.700 に答える
15

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

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}
于 2009-04-17T10:56:48.837 に答える
10

Nippysaurus の答えを説明するには:新しい項目をリストに追加し、同じ列挙中に新しく追加された項目も処理したい場合は、 foreachループの代わりにforループを使用するだけで、問題は解決します:)

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、

リスト(15項目)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

于 2015-08-26T12:04:23.040 に答える
8

前述のとおりですが、コード サンプルを使用すると、次のようになります。

foreach(var item in collection.ToArray())
    collection.Add(new Item...);
于 2009-04-28T23:36:16.397 に答える
4

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

于 2009-04-17T10:51:32.033 に答える
4

列挙されている間は列挙可能なコレクションを変更できないため、列挙の前または後に変更を加える必要があります。

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

また:

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

また

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

于 2009-04-17T12:17:12.763 に答える
3

これを行う方法は次のとおりです(迅速で汚い解決策。この種の動作が本当に必要な場合は、設計を再検討するか、すべての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);
        }
    }
}
于 2009-04-17T11:02:43.223 に答える
3

LINQは、コレクションのジャグリングに非常に効果的です。

あなたのタイプと構造は私にはわかりませんが、あなたの例に私の能力を最大限に合わせようとします.

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

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

より一般的な例として、コレクションを繰り返し処理し、特定の条件が真であるアイテムを削除したいとしましょう。回避foreach、LINQ の使用:

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

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

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));
于 2015-03-30T08:53:51.080 に答える
1

パフォーマンスの観点からの最善のアプローチは、おそらく 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>ましたが、それを行うための効率的な方法を知りません。それでも、配列に対する操作はかなり高速です。注目すべきは、リストにアイテムを追加したりリストから削除したりするために、他のアイテムを「プッシュ」する必要がないという事実です。各アイテムは、配列内の適切な場所に直接書き込まれます。

于 2013-01-27T19:05:03.240 に答える