Comments 36
Интересные моменты. Особенно про утиную типизацию. Если на Dispose еще можно было как то случайно нарваться, то про Enumerator вряд ли. Спасибо за заметку.
-1
Если читать книжки по c#, например, C# in a Nutshell, можно узнать действительно все особенности языка, включая эти особенности foreach :)
+8
await тоже работает на утиной типизации (ищет GetAwaiter).
+1
Кажется, оба нюанса есть в книге Скита. По крайней мере второй точно.
0
Еще тонкость. Для старых компиляторов эквивалентный код был на самом деле такой:
То есть отличалось место определения переменной цикла.
Подробности: habrahabr.ru/post/141270/
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/
+2
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;
}
А потом я задумался, в какой же момент этот файл будет закрыт…0
Без Dispose невозможно было бы отследить изменение коллекции в процессе итеррирования по ней. Ибо нельзя узнать, есть ли читающие пользователи у коллекции или нет. Так же написание многопоточных контейнеров тоже упрощается, так как энумератор это частичка контейнера, которая отдается наружу, и нужно точно знать когда все началось и когда все закончилось.
-2
UFO just landed and posted this here
Можно реализовать следующим способом, отдал энумератор +1, отдал второй еще +1, вызывался диспоз -1 и т.д.
А в методе Add или Remove можно проверять равно ли число 0, если нет, то мы портим коллекцию для читающих пользователей.
Это один из вариантов, можно и другими способами.
А в методе Add или Remove можно проверять равно ли число 0, если нет, то мы портим коллекцию для читающих пользователей.
Это один из вариантов, можно и другими способами.
-2
На самом деле, это реализовано немного по-другому: все операции изменения коллекции инкрементируют её версию. При создании итератора, в нём фиксируется текущая версия коллекции, а далее при продвижении вперед каждый раз проверяется, не изменилась ли версия. Если изменилась, значит коллекция была модифицирована, и, следовательно, надо кинуть исключение.
+6
Допустим, отследить изменение коллекции можно запросто — достаточно счетчика модификаций в коллекции — и его копии в перечислителе. А вот если требуется хотя бы блокировка — тут да, без Dispose никак.
+2
Собственно, именно так оно и реализуется в стандартных коллекциях — поле version в самой коллекции, которое инкрементируется при каждом изменении, и сохраняется в энумераторе в момент его создания — а потом сравнивается в Current/MoveNext. Коллекция о своих энумераторах не знает.
Dispose же нужен для коллекций и прочих перечислений, у которых энумераторы реально владеют какими-то требующими явного высвобождения ресурсами — например, курсором БД, или файловым хэндлом. А также для вызова блоков finally в методах-итераторах.
Dispose же нужен для коллекций и прочих перечислений, у которых энумераторы реально владеют какими-то требующими явного высвобождения ресурсами — например, курсором БД, или файловым хэндлом. А также для вызова блоков finally в методах-итераторах.
+1
Извиняюсь, слово «невозможно» было очень сильным.
0
В стандартном List<T> например хранится int version, который инкрементится на каждое действие, изменяющее структуру листа. При создании итератора (а он является внутренним классом листа) ему в приватное поле копируется эта версия, а на MoveNext проверяется, есть ли разница между исходным version и текущим и если есть, то вызывается исключение. Реализация Dispose пустая.
+1
А в чем неприятность Dispose в foreach? foreach явно создает новый экземпляр энумератора в начале работы, он же его и подчищает в конце, если необходимо. Если бы он этого не делал, как бы вы реализовывали подчистку после foreach? Ведь сам энумератор не доступен, это внутренняя переменная, видимая только компилятору.
0
IEnumerator стал Disposable после добавления yield return. Например:
Когда мы закончим итерироваться по этому генертору (причем, мы же можем закончить даже не доходя до конца), нужно закрыть someDisposable. Поэтому компилятор вычисляет такие структуры и кладет их Dispose в Dispose() метод сгенеренного IEnumerator.
using (var disposable = new SomeDisposable())
{
yield return 1;
yield return 2;
// some code
}
}
Когда мы закончим итерироваться по этому генертору (причем, мы же можем закончить даже не доходя до конца), нужно закрыть someDisposable. Поэтому компилятор вычисляет такие структуры и кладет их Dispose в Dispose() метод сгенеренного IEnumerator.
+1
У Сергея Теплякова есть хороший пост на эту тему habrahabr.ru/post/148905/.
+4
Что за тэги «факты, особенности, тонкости»? Вы понимаете значение словосочетания «ключевые слова»? Как по вашим тэгам можно будет что-то найти в этом кривом поисковике?
0
Дико извиняюсь, поправил, если что то еще нужно добавить, добавлю.
0
Что за тэги «факты, особенности, тонкости»?
Нужны тэги: «скандалы, интриги, расследования».
+3
Dispose также вызывается в деструкторе, если какой-то умник забудет вызвать его «вручную».
0
UFO just landed and posted this here
Для всех стандартных классов, реализующих IDisposable, так лучше?
0
UFO just landed and posted this here
Я не спорю, что человека, который не закрывает ресурсы юзингом, нужно гнать в шею. Но упомянуть про обязательное освобождение в деструкторе, как последнем рубеже обороны, в статье про IDisposable считаю необходимым.
0
Во-первых, это статья — совсем не про IDIsposable. А во-вторых, не каждая реализация IDisposable нуждается в финализаторе. Например, чаще всего бывает, что вызов Dispose всего лишь делегируется другому объекту. В таком случае вызывать Dispose в финализаторе нет необходимости — все ресурсы соберет другой объект самостоятельно.
0
В момент вызова деструктора состояние объектов класса не определен, так что вызывать диспоз в деструкторе не всегда возможно. Так же порядок вызов деструкторов не определен. Я могу вызвать диспоз для себя но для дочерних он может быть уже вызван. Собственно поэтому связка деструктор + диспоз это целый паттерн(ы).
Но как сказали выше, статья не об этом =)
Но как сказали выше, статья не об этом =)
0
Sign up to leave a comment.
Интересные моменты в C# (foreach)