3

Хотел бы спросить про такой паттерн использования SuppressFinalize. Допустим, у меня имеется объект Foo, который хранит в себе какой-нибудь ресурс. Объект Foo ответственен за освобождение выделенного ресурса. Я использую всегда такой подход:

Класс Foo всегда реализует finalize и dispose методы, которые используют внутренний метод, который освобождает ресурсы. При вызове dispose я всегда вызываю SuppressFinalize метод (дабы оптимизировать процесс сборки мусора - не нужно будет хранить данный объект в очереди финализации, ну или например если я забуду явно вызвать dispose метод).

Имеет ли данный подход какие-либо performance недостатки? Не могу понять, будет ли иметь какие-либо "сайд эффекты" такой подход - например что gc всегда подобные объекты будет помещать в очередь финализации

UPDATE: Пример использования (написан на C++/Cli - флажок в CleanResources не используется потому что управляемых полей нет) И еще, я упростил немного код, вызов нативных методов обрамлен try/catch блоком, дабы оборачивать исключения в свой управляемый тип исключения

NativeWrapper::NativeWrapper(void) :
   m_native(new Native())
{
}

NativeWrapper::~NativeWrapper(void)
{
    CleanResources();
    System::GC::SuppressFinalize(this);
}

NativeWrapper::!NativeWrapper(void)
{
CleanResources();
}

void NativeWrapper::CleanResources(void)
{
    delete m_native;
}
Alex Aparin
  • 1,136
  • 1
    Имеет смысл хранить ресурс в управляемой обёртке типа SafeHandle и не заморачиваться с корректной имплементацией IDisposable с неуправляемыми ресурсами (которая очень сложна). Читайте здесь. – VladD Feb 13 '17 at 15:36
  • @VladD, насколько я правильно понимаю, SafeHandle имеет смысл только для WinApi ресурсов использовать. А что если я использую память выделенную из неуправляемого кода? Тогда все как раз сводится к моей реализации? – Alex Aparin Feb 13 '17 at 15:42
  • А как именно вы её аллоцируете? – VladD Feb 13 '17 at 15:43
  • @VladD, обновил вопрос (вставил код) – Alex Aparin Feb 13 '17 at 15:48

1 Answers1

3

Я не вижу недостатков в смысле производительности. Единственный недостаток — IDisposable должен быть идемпотентен, то есть должно быть возможно вызвать Dispose дважды. У вас это на текущий момент приведёт к двойному освобождению памяти, так что имеет смысл завести ещё и флаг isDisposed.

Даже если вы в вашем коде пользуетесь using, всё равно клиент может в любой момент вызвать GC.ReRegisterForFinalize, что приведёт к вызову Finalize даже после Dispose.

(Большой обзор, посвящённый реализации IDisposable, включающий заготовку для кастомной обёртки, тут.)

VladD
  • 206,799
  • Очень хорошее замечание, я это пропустил. Можно еще просто занулить m_native. Правда потом осторожно отслеживать использование этого нативного ресурса (чтобы метод у null не вызвать). А вообще я правильно понимаю, финализатор это просто лишняя страховка? допустим если забыли явно вызвать диспоз – Alex Aparin Feb 13 '17 at 15:54
  • Спасибо, буду изучать код! Очень хорошая ссылка – Alex Aparin Feb 13 '17 at 15:58
  • @LmTinyToon: Возможно, в конец конструктора имеет смысл вставить GC.KeepAlive(this); ещё. Но тут включаются тонкие вопросы многопоточности. – VladD Feb 13 '17 at 15:59
  • @LmTinyToon: Пожалуйста! – VladD Feb 13 '17 at 15:59
  • а что вы имели ввиду под словом using в моем коде? не понял этот момент. – Alex Aparin Feb 13 '17 at 16:00
  • @LmTinyToon: Ну, если ваш объект используется из C#-кода при помощи конструкции using (var w = new NativeWrapper()) { /*сделать что-то с ним*/ }. В этом случае гарантирован вызов Dispose (то есть, ~NativeWrapper()), но при этом, несмотря на SuppressFinalize, финализатор (!NativeWrapper()) тоже может быть вызван. – VladD Feb 13 '17 at 16:03
  • Ага, понял что вы имели ввиду – Alex Aparin Feb 13 '17 at 16:11