コレクションを反復処理しているときにコレクションを変更しないのはなぜですか?
一部のコレクションは反復時に変更できるため、グローバルに悪くはありません。ほとんどの場合、基になるコレクションが変更された場合でも正しく機能する効果的なイテレーターを作成することは非常に困難です。多くの場合、例外はイテレータのライターがパントして、それに対処したくないと言っていることです。
場合によっては、基になるコレクションが変更されたときにイテレータが何をすべきかが明確ではありません。明確なケースもありますが、他のケースでは、異なる人々が異なる行動を期待します。そのような状況にあるときはいつでも、より深い問題があることを示しています(繰り返しているシーケンスを変更してはいけません)。
他の問題を発生させることなく、コレクションを反復処理するときに変更をサポートするコレクションを作成することは可能ですか?(注:最初の答えはこれにも答えることができます)
もちろん。
リストについては、このイテレータを検討してください。
public static IEnumerable<T> IterateWhileMutating<T>(this IList<T> list)
{
for (int i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
基になるリストから現在のインデックスまたはそれ以前のアイテムを削除すると、反復中にアイテムがスキップされます。現在のインデックスまたはその前にアイテムを追加すると、アイテムが複製されます。ただし、反復中に現在のインデックスを超えてアイテムを追加/削除した場合、問題は発生しません。気を付けて、リストからアイテムが削除/追加されたかどうかを確認し、それに応じてインデックスを調整することもできますが、常に機能するとは限らないため、すべてのケースを処理することはできません。のようなものがある場合はObservableCollection
、追加/削除とそのインデックスの通知を受け取り、それに応じてインデックスを調整します。これにより、イテレータが基になるコレクションの変更を処理できるようになります(別のスレッドにない場合)。
缶のイテレータは、ObservableCollection
アイテムがいつ追加/削除されたか、そしてそれらがどこにあるかを知ることができるので、それに応じてその位置を調整することができます。組み込みのイテレータがミューテーションを適切に処理するかどうかはわかりませんが、基になるコレクションのミューテーションを処理するイテレータは次のとおりです。
public static IEnumerable<T> IterateWhileMutating<T>(
this ObservableCollection<T> list)
{
int i = 0;
NotifyCollectionChangedEventHandler handler = (_, args) =>
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewStartingIndex <= i)
i++;
break;
case NotifyCollectionChangedAction.Move:
if (args.NewStartingIndex <= i)
i++;
if (args.OldStartingIndex <= i) //note *not* else if
i--;
break;
case NotifyCollectionChangedAction.Remove:
if (args.OldStartingIndex <= i)
i--;
break;
case NotifyCollectionChangedAction.Reset:
i = int.MaxValue;//end the sequence
break;
default:
//do nothing
break;
}
};
try
{
list.CollectionChanged += handler;
for (i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
finally
{
list.CollectionChanged -= handler;
}
}
シーケンスの「前」からアイテムが削除された場合、アイテムをスキップせずに通常どおり続行します。
アイテムがシーケンスの「前」に追加された場合、そのアイテムは表示されませんが、他のアイテムも2回表示されません。
アイテムが現在の位置の前から後に移動された場合、そのアイテムは2回表示されますが、他のアイテムがスキップまたは繰り返されることはありません。アイテムが現在の位置の後から現在の位置の前に移動された場合、そのアイテムは表示されませんが、それだけです。コレクションの後半から別の場所にアイテムを移動しても問題はなく、結果に移動が表示されます。前の場所から別の前の場所に移動すると、すべて問題なく移動します。イテレータによって「表示」されることはありません。
アイテムの交換は問題ではありません。ただし、現在の位置の「後」にある場合にのみ表示されます。
コレクションをリセットすると、シーケンスは現在の位置で正常に終了します。
このイテレータは、複数のスレッドがある状況を処理しないことに注意してください。別のスレッドが反復しているときに別のスレッドがコレクションを変更すると、悪いことが起こる可能性があります(アイテムがスキップまたは繰り返されたり、インデックスの範囲外の例外などの例外が発生したりすることもあります)。これにより、スレッドが1つしかない、またはイテレーターを移動したりコレクションを変更したりするコードを1つのスレッドだけが実行している、反復中の変更が可能になります。
C#コンパイラが列挙子インターフェイスを生成するとき、実装はこのようなことを考慮に入れますか?
コンパイラーはインターフェース実装を生成しません。人はそうします。