Pull to refresh

Comments 36

Интересные моменты. Особенно про утиную типизацию. Если на Dispose еще можно было как то случайно нарваться, то про Enumerator вряд ли. Спасибо за заметку.
Если читать книжки по c#, например, C# in a Nutshell, можно узнать действительно все особенности языка, включая эти особенности foreach :)
Не читал книгу данных авторов, не хуже Richter'а и Skeet'а?
Книжка отличная, я бы её не стал сравнивать с Рихтером, читать нужно обе: CLR via C# для изучения .NET, C# in a Nutshell — для изучения C#.
Спасибо, не слышал о данной книге. Надо будет взглянуть
await тоже работает на утиной типизации (ищет GetAwaiter).
Кажется, оба нюанса есть в книге Скита. По крайней мере второй точно.
Еще тонкость. Для старых компиляторов эквивалентный код был на самом деле такой:
Container container = new Container();
Enumerator enumerator = container.GetEnumerator();
try
{
    object element;
    while (enumerator.MoveNext())
    {
        element = enumerator.Current;
        // содержимое foreach
    }
}
finally
{
    IDisposable disposable = enumerator as IDisposable;
    if (disposable != null)
        disposable.Dispose();
}

То есть отличалось место определения переменной цикла.

Подробности: habrahabr.ru/post/141270/
Ога, та же тема с индексной переменной в цикле for. Это было исправлено для того, чтобы замыкания в цикле работали так, как «ожидает» большинство разработчиков: т.е. в каждой итерации захватывали актуальное значение элемента/индексной переменной.

Пардон, в ссылку не заглянул сначала :).
UFO just landed and posted this here
UFO just landed and posted this here
Декомпилятором, очевидно же…

Лично я сначала просто написал:
public IEnumerable<string> Lines(string path) {
  string line;
  using (var file = File.OpenText(Path.Combine(basepath, path)))
    while ((line = file.ReadLine()) != null)
      yield return line;
}
А потом я задумался, в какой же момент этот файл будет закрыт…
Без Dispose невозможно было бы отследить изменение коллекции в процессе итеррирования по ней. Ибо нельзя узнать, есть ли читающие пользователи у коллекции или нет. Так же написание многопоточных контейнеров тоже упрощается, так как энумератор это частичка контейнера, которая отдается наружу, и нужно точно знать когда все началось и когда все закончилось.
UFO just landed and posted this here
Можно реализовать следующим способом, отдал энумератор +1, отдал второй еще +1, вызывался диспоз -1 и т.д.
А в методе Add или Remove можно проверять равно ли число 0, если нет, то мы портим коллекцию для читающих пользователей.
Это один из вариантов, можно и другими способами.
На самом деле, это реализовано немного по-другому: все операции изменения коллекции инкрементируют её версию. При создании итератора, в нём фиксируется текущая версия коллекции, а далее при продвижении вперед каждый раз проверяется, не изменилась ли версия. Если изменилась, значит коллекция была модифицирована, и, следовательно, надо кинуть исключение.
Допустим, отследить изменение коллекции можно запросто — достаточно счетчика модификаций в коллекции — и его копии в перечислителе. А вот если требуется хотя бы блокировка — тут да, без Dispose никак.
Собственно, именно так оно и реализуется в стандартных коллекциях — поле version в самой коллекции, которое инкрементируется при каждом изменении, и сохраняется в энумераторе в момент его создания — а потом сравнивается в Current/MoveNext. Коллекция о своих энумераторах не знает.

Dispose же нужен для коллекций и прочих перечислений, у которых энумераторы реально владеют какими-то требующими явного высвобождения ресурсами — например, курсором БД, или файловым хэндлом. А также для вызова блоков finally в методах-итераторах.
Извиняюсь, слово «невозможно» было очень сильным.
В стандартном List<T> например хранится int version, который инкрементится на каждое действие, изменяющее структуру листа. При создании итератора (а он является внутренним классом листа) ему в приватное поле копируется эта версия, а на MoveNext проверяется, есть ли разница между исходным version и текущим и если есть, то вызывается исключение. Реализация Dispose пустая.
А в чем неприятность Dispose в foreach? foreach явно создает новый экземпляр энумератора в начале работы, он же его и подчищает в конце, если необходимо. Если бы он этого не делал, как бы вы реализовывали подчистку после foreach? Ведь сам энумератор не доступен, это внутренняя переменная, видимая только компилятору.
IEnumerator стал Disposable после добавления yield return. Например:
 using (var disposable = new SomeDisposable())
 {
  yield return 1;
  yield return 2;

// some code

 }
}


Когда мы закончим итерироваться по этому генертору (причем, мы же можем закончить даже не доходя до конца), нужно закрыть someDisposable. Поэтому компилятор вычисляет такие структуры и кладет их Dispose в Dispose() метод сгенеренного IEnumerator.
Что за тэги «факты, особенности, тонкости»? Вы понимаете значение словосочетания «ключевые слова»? Как по вашим тэгам можно будет что-то найти в этом кривом поисковике?
Дико извиняюсь, поправил, если что то еще нужно добавить, добавлю.
Я тоже извиняюсь, что-то я совсем злой в последнее время, просто накипело, очень часто встречаю топики с бессмысленными тэгами, что приходится постоянно добавлять самому (благо, хоть такая возможность есть) =(
Ну, так уже получше :)
Что за тэги «факты, особенности, тонкости»?

Нужны тэги: «скандалы, интриги, расследования».
Dispose также вызывается в деструкторе, если какой-то умник забудет вызвать его «вручную».
UFO just landed and posted this here
Для всех стандартных классов, реализующих IDisposable, так лучше?
UFO just landed and posted this here
Я не спорю, что человека, который не закрывает ресурсы юзингом, нужно гнать в шею. Но упомянуть про обязательное освобождение в деструкторе, как последнем рубеже обороны, в статье про IDisposable считаю необходимым.
Во-первых, это статья — совсем не про IDIsposable. А во-вторых, не каждая реализация IDisposable нуждается в финализаторе. Например, чаще всего бывает, что вызов Dispose всего лишь делегируется другому объекту. В таком случае вызывать Dispose в финализаторе нет необходимости — все ресурсы соберет другой объект самостоятельно.
В момент вызова деструктора состояние объектов класса не определен, так что вызывать диспоз в деструкторе не всегда возможно. Так же порядок вызов деструкторов не определен. Я могу вызвать диспоз для себя но для дочерних он может быть уже вызван. Собственно поэтому связка деструктор + диспоз это целый паттерн(ы).
Но как сказали выше, статья не об этом =)
Поэтому они перед началом работы проверяют флаг IsDisposed, все верно.

И да, речь действительно не об этом, поэтому я и на предыдущий комментарий не ответил. Засим завершим беседу.
Sign up to leave a comment.

Articles