2017-01-10 9 views
2

を反復処理するときにこのコードを実行する場合:コレクション列挙子

var list = new List<string> 
{ 
    "foo", 
    "bar", 
}; 

foreach (var l in list) 
{ 
    Console.WriteLine(l); 

    list.Add("bar"); 
} 

例外がスローされます。

のSystem.InvalidOperationException:コレクションが変更されました。列挙操作が実行されないことがあります。

列挙子がそのコレクションを反復処理している間に、コレクションが変更されたことを.NETがどのように知っていますか?コレクションオブジェクトにこれのフラグがありますか?

+1

*それは、実行時間の詳細ではなく、コンパイル時一つだとして**、この事実を知らないどのようにC#コンパイラが知っている*コンパイラ: ) –

答えて

7

List<T>は整数変数に内部的に「バージョン」を保持します。リストの各変更(Add、Remove、Sort、Clear、...)は、このバージョンを1つずつインクリメントします。

List<T>の列挙子が初期化されると、このバージョン番号が保存されます。各MoveNext()に、すなわち各反復ごとに、リストのバージョン番号がまだ保存されたバージョン番号と等しいかどうかがチェックされる。

この方法では、リストが2つの繰り返しの間に変更されたときを検出できます。この実装は、整数オーバフローの特有のバグ(Why this code throws 'Collection was modified', but when I iterate something before it, it doesn't?)を引き起こすことに注意してください。

Sort(Comparison<T> comparison)に関するバグもありますが、バージョン番号は増えません。このコード:

var list = new List<string> 
{ 
    "foo", 
    "bar", 
}; 

foreach (var l in list) 
{ 
    Console.WriteLine(l); 
    list.Sort((x, y) => x.CompareTo(y)); 
} 

プリント:

foo 
foo 
+1

@トーマス[提出](https://github.com/dotnet/corefx/issues/15028)。 – CodeCaster

+1

ありがとう!これを広範にクリアするために! –

+0

もちろん、それは[複製](https://github.com/dotnet/coreclr/issues/8071)、@トーマスでした。 :P – CodeCaster

5

はい、標準的な解決策はフラグ、バージョン値などです。 List<T>

https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,cf7f4095e4de7646

の場合には、バージョン例えば

private int _version; 

です

public bool MoveNext() { 
    ... 
    return MoveNextRare() 
} 

private bool MoveNextRare() 
{     
    if (version != list._version) { 
    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); 
} 
関連する問題