C# でいくつかの IEnumerable を反復している間、列挙可能なコレクションの要素を変更できないことが知られています。
// Illegal code
foreach (Employee e in employeeList)
{
e.Salary = 1000000;
}
これがランタイムまたは列挙子自体によってどのように強制されているのだろうか?
制限を誤解していると思います。あなたが投稿したコードは完全に合法であり、実際、そうでない場合、foreach
ループはほとんど役に立たないでしょう.
許可されていないのは、ループの途中でシーケンスを変更することです。foreach
foreach ( Employee e in employeeList )
{
if ( e.Salary > 10000000 )
{
employeeList.Remove(e);
}
}
このコードInvalidOperationException
は実行時にスローします。実装MoveNext
内のへの呼び出しIEnumerator
は、基になるコレクションが呼び出し間で変更されたことを検出するとスローします。これは、列挙子オブジェクトを実装するための要件です。 の各実装はIEnumerable
、この可能性に対処する必要があります。特定のコレクション (.NET 4.0 からの新しい並行コレクション) は、この制限を明示的に適用しないことに注意してください。したがって、上記のコードemployeeList
は、たとえば、ConcurrentDictionary
. 詳細については、このブログ投稿を参照してください。
また、ループ制御変数に値を代入することも正しくありません。たとえば、次のようになります。
foreach ( Employee e in employeeList )
{
if ( e.Salary > 10000000 )
{
e = new Employee { Salary = 999999 };
}
}
これが許可されていない理由は、あまり意味がなく、ほぼ間違いなくバグであるためです。詳細については、この回答を参照してください: Why is The Iteration Variable in a C# foreach statement read-only?
これはシーケンス内の要素を変更しようとしているのではなく、ループ制御変数として使用されるローカル変数の値を変更しようとしているだけであることに注意してください。また、これは列挙子やランタイムによって強制されません。これはコンパイル時エラーであり、C# コンパイラによって強制されます。
あなたの言っていることはまったく真実ではありません。このコードは完全に合法です。
foreach (Employee e in employeeList){
e.Salary = 1000000;
}
しかし、そうではありません。
foreach (Employee e in employeeList){
e = new Employee();
}
制限(他の回答に記載されているように、コードに違反していない)は、列挙子によって強制されます-そうすることを選択した場合。List<T>
コレクションが変更されたときに更新され、各反復でチェックされる内部バージョン番号を保持するなど、組み込みのコレクションのいくつかは信じています。
などの他のクラスはこれをチェックしませんが、同じスレッド内にあるかどうかにかかわらず、コレクションが変更されたときの列挙子の動作に関して他の保証を行います。ConcurrentBag