О шаблоне Dispose и Finalizer в C#

Сначала на этой странице MSDN

есть стандартный шаблон Dispose. И есть bool в качестве параметра защищенного метода Dispose, чтобы сообщить сборщику мусора, освобождены ли уже управляемые ресурсы вручную, чтобы сборщику мусора не нужно было о них заботиться.

Теперь вопрос, что именно нужно сделать внутри блока if (disposing) {}? Обычно сборщик мусора очищает управляемые ресурсы, поэтому ничего особенного делать не нужно. Но поскольку внутри этого блока нужно явно очищать управляемые ресурсы, означает ли это, что нужно просто установить для всех полей и прочего в объекте значение null?

Во-вторых, не лучше ли иметь в языке только один деструктор (или финализатор, как он там называется)? А потом в дизайне GC просто поставить бит, чтобы определить, вызван ли уже деструктор, чтобы не было необходимости собирать его мусором, или деструктор еще не вызван и GC должен его подчистить. Я нашел шаблон Dispose довольно сложным, и я очень запутался, что очищать в какой функции и как очищать в производных классах. И, используя схему деструктора signle, GC просто очищает вещи, когда они еще не очищены, и не очищает, когда они уже очищены.

Привет

PS: Значит, это также хороший и простой шаблон для очистки объектов?

class Foo
{
    bool unmanagedDisposed = false;
    void Dispose() {/*clean up unmanaged resources*/ unmanagedDisposed = true;}
    ~Foo() {if (!unmanagedDisposed) Dispose();}
}

Так что если программист знает и не забывает вызывать Dispose(), то в финализаторе ничего не делать, иначе в финализаторе убирать неуправляемые ресурсы. И здесь нам не нужно заботиться об этих управляемых ресурсах.


person Weixiang Guan    schedule 28.01.2013    source источник
comment
Прочитайте эту замечательную статью, чтобы полностью понять шаблон Dispose. codeproject.com/Articles/29534/ Довольно сложно для первого прочтения, но чрезвычайно поучительно.   -  person Cédric Bignon    schedule 28.01.2013
comment
Представьте, что один из управляемых ресурсов вашего класса сам реализует IDisposable. Как вы думаете, где вы должны вызвать Dispose() по этому поводу?   -  person Damien_The_Unbeliever    schedule 28.01.2013


Ответы (2)


что именно нужно сделать внутри блока if (disposing) {}?

Вы очищаете управляемые ресурсы, т. е. вызываете Dispose() для всех объектов IDisposable, которыми вы владеете на данный момент.

чтобы явно очистить управляемые ресурсы, означает ли это, что нужно просто установить все поля и прочее в объекте равными нулю?

Нет, это не значит. Речь идет только об объектах IDisposable.

не лучше ли иметь только один деструктор (или финализатор, как бы он ни назывался) в языке?

У нас есть только один деструктор, он же Finalizer, и Dispose() им не является. Это делает остальную часть вашего абзаца неактуальной.

У нас есть шаблон Disposable и GC, они связаны и взаимодействуют, но не одно и то же. Сборщик мусора управляет памятью и только памятью. IDisposable предназначен для управления ресурсами (поток, соединения, растровые изображения).

person Henk Holterman    schedule 28.01.2013
comment
Я всегда нахожу идею нулевого сравнения с удалением странным выводом, который любой может сделать интуитивно. - person Grant Thomas; 28.01.2013
comment
@henk-holterman Спасибо за конкретную инструкцию по блоку if (disposing){}! В любом случае, под деструктором или финализатором я подразумеваю объединение всей процедуры уничтожения, так что не нужно заботиться о финализаторе и интерфейсе удаления. Мне все еще нужно прояснить это для моего onw. - person Weixiang Guan; 28.01.2013

По сути, вам нужно реализовать полный шаблон ~Destructor() с dispose только в том случае, если у вас есть класс с неуправляемыми полями или свойствами.

Если все ваши поля и свойства являются управляемыми и не одноразовыми, вам вообще не нужно реализовывать IDisposable.

Если одно из ваших полей или свойств является одноразовым, вам нужно просто реализовать шаблон IDisposable. Вам не обязательно делать все возможное с приведенным выше шаблоном.

Если в последнем случае у вас есть неуправляемый ресурс в поле или свойстве (например, собственный указатель на что-то, соединение с базой данных, отличное от ADO, или другие управляемые соединения), то .net не знает, как его очистить, когда сборщик мусора катается вокруг.

В этом случае вам нужно рассмотреть два места, где ваш объект будет очищаться. Либо он будет очищен разработчиком, вызвавшим Dispose(), как он должен, либо он забудет. Если он забывает, а у вас есть деструктор, то он ставится в очередь на доработку.

Вот тут-то и появляется вызов Dispose(disposing). Если разработчик хороший и вызывает Dispose(), вы отправляете true, чтобы можно было также очистить управляемые ресурсы.

Если разработчик не вызвал dispose и плохой объект попадает в очередь финализации, то вызов Dispose() для управляемых объектов вызовет исключение, поскольку они больше не существуют. Таким образом, в этом случае вы отправляете False, чтобы управляемые ресурсы были пропущены и чтобы избежать исключения. Я не уверен в этом, но легенды гласят, что выбрасывание исключения в очередь финализации прерывает весь процесс и даже может привести к концу света. Так что не делай этого.

person Mike the Tike    schedule 28.01.2013
comment
Предостережение: если одно из ваших полей или свойств является одноразовым... абзац - если класс не sealed (к сожалению, не по умолчанию), и вы реализуете одноразовый, вы должны пойти на все, потому что вы не знаете какие производные классы будут нуждаться или делать. - person Damien_The_Unbeliever; 28.01.2013
comment
@MiketheTike Спасибо за ваш четкий ответ, после прочтения вашего ответа у меня появилось лучшее понимание. Поэтому я думаю, что финализатор просто гарантирует, что неуправляемые ресурсы будут очищены независимо от того, вызывает ли программист Dispose или нет. А так как вместе с неуправляемыми тоже можно было бы чистить управляемые ресурсы, то нам нужно отдельно обрабатывать управляемые ресурсы в Dispose и финализаторе. - person Weixiang Guan; 28.01.2013
comment
@Damien_The_Unbeliever: Так думали в Microsoft, когда впервые написали шаблон Dispose. Однако на практике, если базовый класс не инкапсулирует какие-либо неуправляемые ресурсы, производные классы тоже не должны этого делать. Любые неуправляемые ресурсы, которые им могут понадобиться, должны быть инкапсулированы в их собственные финализируемые классы (возможно, вложенные в производный класс), которые затем производный класс может использовать в качестве управляемого ресурса. - person supercat; 26.02.2013