Недокументированная особенность класса List<T>

Несмотря на то, что использование цикла foreach в принципе не комильфо, многие его используют. Для поклонников этого цикла и предлается следующая информация.
Не секрет, что, если в теле цикла foreach Вы попытаетесь изменить коллекцию List<T>, Вы получите InvalidOperationException. Однако это происходит не всегда.

В результате подробного изучения кода платформы, стало ясно, что у данного правила есть единственное исключение: метод Sort(Comparison<T> comparison).
Это баг Microsoft’а, и они о нем знают с моей подачи. Но, зная их, думаю, пару-тройку лет можно смело этой дыркой пользоваться.
Другое дело, что, в результате использования данного метода внутри цикла, состояние коллекции действительно становится неопределенным, так что используйте это знание на свой страх и риск.

Пример, вызывающий InvalidOperationException, как и положено:

C#:Select code
List<int> list = new List<int>(Enumerable.Range(1, 10));
int total = 0;
foreach (int i in list)
{
    total += i;
    list.Sort();
}
MessageBox.Show(total.ToString());

Пример использования дырки мелкософта:

C#:Select code
List<int> list = new List<int>(Enumerable.Range(1, 10));
int total = 0;
foreach (int i in list)
{
    total += i;
    list.Sort((a, b) => b.CompareTo(a));
}
MessageBox.Show(total.ToString());

Удачи!

Обсуждение на форуме: http://usings.ru/forum/viewtopic.php?t=12

admin опубликовано 2009-8-29 Рубрика: Общая информация | Метки: , , , ,

2 ответов Оставить комментарий

  1. #1drinian @ 2009-9-18 09:54 Ответ

    Спасибо, про этот баг не знал.

    Только состояние списка вполне определенное будет.
    В первый раз i будет взято из неотсортированного списка: i = 1.
    Потом ты каждый раз сортируешь список по убыванию, i будет
    равен соответственно 9 (потому что первый элемент уже прошли), 8, …, 1.

    Обычно, модифицирующие коллекцию методы изменяют некоторый внутренний идентификатор версии. Так список узнает, был ли он изменен на предыдущем шаге.
    В методе «List.Sort(Comparison c)» забыли в конце поставить «_version++».

    Только вероятность нарваться на это IMHO довольно мала. Ведь полностью отсортировывать коллекцию на каждой итерации — очень сомнительное решение, как с точки зрения быстродействия, так и с точки зрения логики (в приведенном примере коллекция отсортируется на второй итерации и далее изменяться не будет).

  2. #2admin @ 2009-9-18 11:44 Ответ

    Все верно. И в данном примере действительно состояние списка вполне определенное.
    Однако, если предположить, что внутри цикла исходный код компаратора неизвестен (например, компаратор был передан в качестве параметра некого метода, содержащего этот цикл), то состояние коллекции определить будет уже невозможно.
    И да, действительно, они забыли добавить «_version++» в конец метода и забыли это еще в версии .NET 2.0, а на мой багрепорт они отреагировали вполне ожидаемо:
    «We’ve looked into this issue, and unfortunately it’s too late to make this change for .NET Framework 4. I’m going to keep this issue open so that we can track it for the next version of the framework.»
    Так что ждем версии 4.5 или 5.0.

Ответить

(Ctrl + Enter)