2016-01-29 23 views
8

Po tym pytaniu Foreach loop for disposing controls skipping iterations to podsłuch mi, że iteracja pozwolono na zmieniającej się kolekcji:Dlaczego funkcja ControlCollection NIE rzuca wyjątku InvalidOperationException?

Na przykład następujący:

List<Control> items = new List<Control> 
{ 
    new TextBox {Text = "A", Top = 10}, 
    new TextBox {Text = "B", Top = 20}, 
    new TextBox {Text = "C", Top = 30}, 
    new TextBox {Text = "D", Top = 40}, 
}; 

foreach (var item in items) 
{ 
    items.Remove(item); 
} 

rzuca

InvalidOperationException: Kolekcja została zmodyfikowana; operacja wyliczania może nie zostać wykonana.

Jednak w .Net Formularza można zrobić:

this.Controls.Add(new TextBox {Text = "A", Top = 10}); 
this.Controls.Add(new TextBox {Text = "B", Top = 30}); 
this.Controls.Add(new TextBox {Text = "C", Top = 50}); 
this.Controls.Add(new TextBox {Text = "D", Top = 70}); 

foreach (Control control in this.Controls) 
{ 
    control.Dispose(); 
} 

który pomija elementy ponieważ iterator biegnie nad zmieniającym kolekcji, bez rzuca wyjątek

błąd? czy iteratory nie są wymagane do rzucenia InvalidOperationException, jeśli kolekcja podkładania się zmieni?

Moje pytanie brzmi: Dlaczego iteracja zmieniającego się ControlCollection NIE powoduje odrzucenia wyjątku InvalidOperationException?

Uzupełnienie:

Dokumentacja IEnumerator mówi:

moduł wyliczający nie mają wyłączny dostęp do kolekcji; dlatego wyliczanie poprzez kolekcję nie jest samoistnie procedurą bezpieczną dla wątków. Nawet jeśli kolekcja jest zsynchronizowana, inne wątki nadal mogą modyfikować kolekcję, , co powoduje, że moduł wyliczający wyrzuca wyjątek.

https://msdn.microsoft.com/en-us/library/system.collections.ienumerator%28v=vs.100%29.aspx

+0

robi 'this.Controls.Count' po zakończeniu" foreach "? Myślę, że controllCollection się nie zmienia. –

+0

@AmitKumarGhosh Tak, ponieważ ControllsCollection przestawia indeks pozostałych kontrolek po każdym wywołaniu funkcji 'Control.ControlCollection.Remove()' – Marco

+0

Nie wszystkie kolekcje implementują tę funkcję. –

Odpowiedz

5

Odpowiedź na to pytanie można znaleźć w the Reference Source for ControlCollectionEnumerator

private class ControlCollectionEnumerator : IEnumerator { 
    private ControlCollection controls; 
    private int current; 
    private int originalCount; 

    public ControlCollectionEnumerator(ControlCollection controls) { 
     this.controls = controls; 
     this.originalCount = controls.Count; 
     current = -1; 
    } 

    public bool MoveNext() { 
     // VSWhidbey 448276 
     // We have to use Controls.Count here because someone could have deleted 
     // an item from the array. 
     // 
     // this can happen if someone does: 
     //  foreach (Control c in Controls) { c.Dispose(); } 
     // 
     // We also dont want to iterate past the original size of the collection 
     // 
     // this can happen if someone does 
     //  foreach (Control c in Controls) { c.Controls.Add(new Label()); } 

     if (current < controls.Count - 1 && current < originalCount - 1) { 
      current++; 
      return true; 
     } 
     else { 
      return false; 
     } 
    } 

    public void Reset() { 
     current = -1; 
    } 

    public object Current { 
     get { 
      if (current == -1) { 
       return null; 
      } 
      else { 
       return controls[current]; 
      } 
     } 
    } 
} 

Należy zwrócić szczególną uwagę na komentarze w MoveNext() który wyraźnie Ten adres.

IMO to błędna "poprawka", ponieważ maskuje oczywisty błąd, wprowadzając subtelny (elementy są po cichu pomijane, jak zauważył OP).

+0

+1 Więc to wyjaśnia, jak to unika. Ale czy nie powinien on wrzucać wyjątku InvalidOperationException? Dokumentacja oznaczałaby, że powinien to zrobić każdy IEumerator. Jeśli ta implikacja jest poprawna, to czy to błąd? Ale biorąc pod uwagę komentarze w kodzie źródłowym, wyraźnie go unikając. Bardzo dziwne. –

+0

@MeirionHughes Zgadzam się - wygląda na to, że próbowali naprawić postrzegany problem, a przez to pogorszyli sytuację (jeśli uważasz, że jest gorzej, gdy masz cichy błąd zamiast głośnego błędu, który robię) –

+0

Aż do kogoś z ms -team wyjaśnia, DLACZEGO wyraźnie nie chcieli rzucić 'InvalidOperationException', to będzie zaakceptowana odpowiedź. :) –