Правила использования финалайзеров [blog]
От: mihailik Украина  
Дата: 16.01.06 17:37
Оценка: 6 (1)
Как известно, .NET Framework предоставляет объектам перед уничтожением "последнее желание", Finalize. К несчастью, многие плохо понимают чем такие вещи чреваты.

<b>Финализаторы и деструкторы в C#</b>

Правила:
1. Финализатор имеет смысл только если непосредственно ваш класс хранит в себе Handle внешнего ресурса (за редчайшим исключением)

2. Если вас потянуло на Finalize, постарайтесь переключиться на IDisposable, это и безвредно, и полезно

3. При переопределении метода Dispose(bool) выполняйте очистительные действия только если disposing==true (в противном случае очистку сделают и без вас)

30.01.06 00:08: Перенесено из '.NET'
Re: Правила использования финалайзеров [blog]
От: GlebZ Россия  
Дата: 16.01.06 17:47
Оценка:
Здравствуйте, mihailik, Вы писали:

Ага, и выучить Dispose Pattern назубок. Сейчас как раз наблюдаю крутую очистку в финалайзере.

С уважением, Gleb.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Правила использования финалайзеров [blog]
От: R0man Украина  
Дата: 16.01.06 19:30
Оценка: +2
Здравствуйте, mihailik, Вы писали:

M>Как известно, .NET Framework предоставляет объектам перед уничтожением "последнее желание", Finalize. К несчастью, многие плохо понимают чем такие вещи чреваты.


M><b>Финализаторы и деструкторы в C#</b>


M>Правила:

M>1. Финализатор имеет смысл только если непосредственно ваш класс хранит в себе Handle внешнего ресурса (за редчайшим исключением)

M>2. Если вас потянуло на Finalize, постарайтесь переключиться на IDisposable, это и безвредно, и полезно


M>3. При переопределении метода Dispose(bool) выполняйте очистительные действия только если disposing==true (в противном случае очистку сделают и без вас)


А в чем заключаются провалы в методологии Рихтера, на Ваш вгляд, в контексте моделей особождения ресурсов?
Re[2]: Правила использования финалайзеров [blog]
От: mihailik Украина  
Дата: 17.01.06 07:40
Оценка: +1
R>А в чем заключаются провалы в методологии Рихтера, на Ваш вгляд, в контексте моделей особождения ресурсов?

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

Кроме того, в частности:

По Рихтеру складывается впечатление, что Dispose КАК ПРАВИЛО реализуют одновременно с Finalize.

Рихтер описывает шаблон правильного совмещения IDisposable и Finalizable, не говоря о том, в каких случаях он применим.

* * *

В итоге, программисты-новички начинают думать о Finalize как о средстве, нередко необходимом в собственных классах. Наподобие "если класс инкапсулирует ресурс, ему нужен Dispose и Finalize". НЕВЕРНО!!!

Методологически правильно:
— показать, что Finalize существует;
— обяснить, зачем он существует;
— быстро и внятно объяснить, что ему редко есть применение в собственных классах;
— и тут же уводить разговор к Dispose.
Re[2]: Правила использования финалайзеров [blog]
От: mihailik Украина  
Дата: 17.01.06 07:45
Оценка:
GZ>Ага, и выучить Dispose Pattern назубок. Сейчас как раз наблюдаю крутую очистку в финалайзере.

Хм, статья в MSDN сильно страдает теми же тараканами:

Во-первых, название Design Pattern сразу обманывает читателя.

Во-вторых, в тексте попросту НЕ ОБЪЯСНЯЕТСЯ, как Dispose связано с Finalize. Поэтому подразумевается, что это причинно-следственная связь. То есть, Design Pattern ТРЕБУЕТ Finalize? Ересь!
Re[3]: Правила использования финалайзеров [blog]
От: R0man Украина  
Дата: 17.01.06 07:46
Оценка:
Здравствуйте, mihailik, Вы писали:

R>>А в чем заключаются провалы в методологии Рихтера, на Ваш вгляд, в контексте моделей особождения ресурсов?


M>Он очень много и увлекательно описывает финализаторы, не слишком заостряя внимание на том, что они

M>а) сложны и небезопасны
M>б) редко применимы

M>Кроме того, в частности:


M>По Рихтеру складывается впечатление, что Dispose КАК ПРАВИЛО реализуют одновременно с Finalize.


Ну... Каждый читает по-своему...

M>Рихтер описывает шаблон правильного совмещения IDisposable и Finalizable, не говоря о том, в каких случаях он применим.


M>* * *


M>В итоге, программисты-новички начинают думать о Finalize как о средстве, нередко необходимом в собственных классах. Наподобие "если класс инкапсулирует ресурс, ему нужен Dispose и Finalize". НЕВЕРНО!!!


M>Методологически правильно:

M>— показать, что Finalize существует;
M>— обяснить, зачем он существует;
M>— быстро и внятно объяснить, что ему редко есть применение в собственных классах;
M>— и тут же уводить разговор к Dispose.

Должен не согласиться, я как раз "новичек", прочитал недавно Рихтера, из выше сказанного в моей голове отразилось только указанное Вами в пункте "Методологически правильно". Может быть мне повезло!
Re[4]: Правила использования финалайзеров [blog]
От: mihailik Украина  
Дата: 17.01.06 08:08
Оценка:
M>>— и тут же уводить разговор к Dispose.

R>Должен не согласиться, я как раз "новичек", прочитал недавно Рихтера, из выше сказанного в моей голове отразилось только указанное Вами в пункте "Методологически правильно". Может быть мне повезло!


Спасибо, Роман.
Для меня это тоже полезный случай на заметку, хотя и нехарактерный.
Re[4]: Правила использования финалайзеров [blog]
От: Воронков Василий Россия  
Дата: 17.01.06 15:40
Оценка: :))
Здравствуйте, R0man, Вы писали:

Может вы разные издания читаете?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Правила использования финалайзеров [blog]
От: Sinclair Россия https://github.com/evilguest/
Дата: 20.01.06 10:30
Оценка: +3
Здравствуйте, mihailik, Вы писали:

GZ>>Ага, и выучить Dispose Pattern назубок. Сейчас как раз наблюдаю крутую очистку в финалайзере.


M>Хм, статья в MSDN сильно страдает теми же тараканами:


M>Во-первых, название Design Pattern сразу обманывает читателя.


M>Во-вторых, в тексте попросту НЕ ОБЪЯСНЯЕТСЯ, как Dispose связано с Finalize. Поэтому подразумевается, что это причинно-следственная связь. То есть, Design Pattern ТРЕБУЕТ Finalize? Ересь!

Вообще, я до сих пор не могу понять этих приплясываний с bool параметром.
почему нельзя сделать вот так:
public class BaseResource: IDisposable
{
  private bool disposed = false;
  public void Dispose()
  {
    if (!disposed)
        {
      DisposeManaged();
            DisposeUnmanaged();
            disposed = true;
      GC.SuppressFinalize(this);
        }
  }

  // Dispose managed resources.
  protected virtual void DisposeManaged()
  {
    Components.Dispose();
  }
  // Release unmanaged resources. 
    protected virtual void DisposeUnmanaged()
    {
        CloseHandle(handle);
        handle = IntPtr.Zero;
  }

   // Do not provide destructors in types derived from this class.
   ~BaseResource()      
   {
      DisposeUnmanaged(false);
   }

   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}

// Design pattern for a derived class.
// Note that this derived class inherently implements the 
// IDisposable interface because it is implemented in the base class.
public class MyResourceWrapper: BaseResource
{
   // A managed resource that you add in this derived class.
   private ManagedResource addedManaged;
   // A native unmanaged resource that you add in this derived class.
   private NativeResource addedNative;
   private bool disposed = false;

  // Constructor for this object.
   public MyResourceWrapper()
   {
      // Insert appropriate constructor code here.
   }

  protected override void DisposeManaged()
  {
        addedManaged.Dispose();         
        base.DisposeManaged();
    }
    protected override void DisposeUnmanaged()
    {
        CloseHandle(addedNative);
        base.DisposeUnmanaged();
  }
}

По-моему, так гораздо яснее, и труднее сделать ошибку. Можно еще подумать на тему иерархии классов, где не все обязаны иметь оба типа ресурсов, нуждающихся в финализации.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Правила использования финалайзеров [blog]
От: Alexander__S  
Дата: 20.01.06 21:01
Оценка:
Здравствуйте, Sinclair, Вы писали:

Оверквотинг удален.

S>По-моему, так гораздо яснее, и труднее сделать ошибку. Можно еще подумать на тему иерархии классов, где не все обязаны иметь оба типа ресурсов, нуждающихся в финализации.


Не посчитайте за мелкие придирки, но хотелось бы прояснить некоторые вещи...

Ну метод Dispose() c Finalize() сам по себе не связан никак. Связь определяет реалзиция программистом модели освобождения ресурсов. Я думаю, Вы именно это и имели в виду. Для полноты картины хотелось бы увидеть в листинге хотя бы псевдокод метода DisposeUnmanaged(bool disposing) . ИМХО, GC.SuppressFinalize(this); корректнее всатвить перед вызовами DisposeManaged(); DisposeUnmanaged();.

Так в чем же все-таки существенное упрощение? Ну вводится локальная переменная вместо bool параметра, отдельные виртуальные методы очистки управляемых и неуправляемых ресурсов, которые классы-наследники должны переопределить при необходимости, и все это надо будет держать в голове и реализовывать программисту, так же, как и в случае стандартного паттерна.

Все-таки то, что приводится в MSDN — паттерн, рекомендованный в общем случае, и, ИМХО, в этой связи сложно придумать что-либо более простое. Конечно же, то, что написано в MSDN, не является приказом, не подлежащим обсуждению, и ничто не мешает реализовать свою модель освобождения ресурсов, как в Вашем случае, и пользоваться ей. Единственное, что способствутет реализации программистом именно интерфейса IDisposable — это языковая поддержка (using, генерирующий блок try... finally с вызовом в последнем Dispose()), ну и само назначение этого интерфейса (реализованного во многих клссах FW) в контексте модели освобождения ресурсов.

P.S.
я бы добавил также lock в Dispose, и еще: throw new ObjectDisposedException(); компилироваться не будет
Re: Правила использования финалайзеров [blog]
От: Alexander__S  
Дата: 20.01.06 21:35
Оценка:
Здравствуйте, mihailik, Вы писали:

M><b>Финализаторы и деструкторы в C#</b>


Финлазиторы плохи, спору нет. Но за все надо платить, в том числе и за GC.
Re[5]: Правила использования финалайзеров [blog]
От: Sinclair Россия https://github.com/evilguest/
Дата: 22.01.06 09:38
Оценка:
Здравствуйте, Alexander__S, Вы писали:
A__>Не посчитайте за мелкие придирки, но хотелось бы прояснить некоторые вещи...

A__>Ну метод Dispose() c Finalize() сам по себе не связан никак. Связь определяет реалзиция программистом модели освобождения ресурсов. Я думаю, Вы именно это и имели в виду. Для полноты картины хотелось бы увидеть в листинге хотя бы псевдокод метода DisposeUnmanaged(bool disposing) .

Ну, это я опечатался. Нету у него никакого параметра.
A__>ИМХО, GC.SuppressFinalize(this); корректнее всатвить перед вызовами DisposeManaged(); DisposeUnmanaged();.

A__>Так в чем же все-таки существенное упрощение? Ну вводится локальная переменная вместо bool параметра

Нет. Локальная переменная уже была. Просто вместо параметра с невнятной семантикой вводится два различных метода с, как мне кажется, более внятной семантикой.
Единственное различие — в МС-версии сначала освобождаются все ресурсы наследника, затем его предка и т.д. В моей версии сначала освобождаются все управляемые ресурсы, потом все неуправляемые.
A__>, отдельные виртуальные методы очистки управляемых и неуправляемых ресурсов, которые классы-наследники должны переопределить при необходимости, и все это надо будет держать в голове и реализовывать программисту, так же, как и в случае стандартного паттерна.
Схема реализации каждого из методов — проще.
A__>Все-таки то, что приводится в MSDN — паттерн, рекомендованный в общем случае, и, ИМХО, в этой связи сложно придумать что-либо более простое. Конечно же, то, что написано в MSDN, не является приказом, не подлежащим обсуждению, и ничто не мешает реализовать свою модель освобождения ресурсов, как в Вашем случае, и пользоваться ей. Единственное, что способствутет реализации программистом именно интерфейса IDisposable — это языковая поддержка (using, генерирующий блок try... finally с вызовом в последнем Dispose()), ну и само назначение этого интерфейса (реализованного во многих клссах FW) в контексте модели освобождения ресурсов.
С этим никто не спорит.
A__>я бы добавил также lock в Dispose, и еще: throw new ObjectDisposedException(); компилироваться не будет
Это код от МС.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Правила использования финалайзеров [blog]
От: TK Лес кывт.рф
Дата: 22.01.06 14:35
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>По-моему, так гораздо яснее, и труднее сделать ошибку. Можно еще подумать на тему иерархии классов, где не все обязаны иметь оба типа ресурсов, нуждающихся в финализации.


Между тем, они уже есть... Например, флаг disposed из BaseResource в MyResourceWrapper уже не доступен. А объявленное поле никак не используется... DisposeUnmanaged(false) в финализаторе тоже никак не понятен.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[6]: Правила использования финалайзеров [blog]
От: Alexander__S  
Дата: 22.01.06 16:07
Оценка:
S>Нет. Локальная переменная уже была.

A__>>я бы добавил также lock в Dispose, и еще: throw new ObjectDisposedException(); компилироваться не будет

S>Это код от МС.

Да, действительно. Извиняюсь за то, что не удосужился заглянуть в MSDN. Держал в голове рихтеровскую реализацию. Что ж, можно объявить конкурс на лучшую реализацию паттерна освобождения ресурсов
Re[5]: Правила использования финалайзеров [blog]
От: Sinclair Россия https://github.com/evilguest/
Дата: 23.01.06 09:01
Оценка:
Здравствуйте, TK, Вы писали:

TK>Здравствуйте, Sinclair, Вы писали:


S>>По-моему, так гораздо яснее, и труднее сделать ошибку. Можно еще подумать на тему иерархии классов, где не все обязаны иметь оба типа ресурсов, нуждающихся в финализации.


TK>Между тем, они уже есть... Например, флаг disposed из BaseResource в MyResourceWrapper уже не доступен. А объявленное поле никак не используется... DisposeUnmanaged(false) в финализаторе тоже никак не понятен.

Виноват, писал с листа. Вот еще один подход:

public class BaseResource: IDisposable
{
  private bool _disposed = false;
    public bool Disposed { 
    [DebuggerStepThrough]
    get { return _disposed; }
    
    #region IDisposable implementation
  public void Dispose()
  {
    if (!disposed)
    {
      DisposeManaged();
      DisposeUnmanaged();
      _disposed = true;
      GC.SuppressFinalize(this);
    }
  }
    #endregion
    
  // Dispose managed resources.
  protected virtual void DisposeManaged()
  {
    Components.Dispose();
  }
  // Release unmanaged resources. 
  protected virtual void DisposeUnmanaged()
  {
    CloseHandle(handle);
    handle = IntPtr.Zero;
  }

   // Do not provide destructors in types derived from this class.
   ~BaseResource()      
   {
      DisposeUnmanaged();
   }

    protected void CheckDisposed()
    {
    if(Disposed)
    {
       throw new ObjectDisposedException();
    }
    }
  public void DoSomething()
  {
      CheckDisposed();
  }
}

public class MyResourceWrapper: BaseResource
{
   // A managed resource that you add in this derived class.
   private ManagedResource addedManaged;
   // A native unmanaged resource that you add in this derived class.
   private NativeResource addedNative;

  protected override void DisposeManaged()
  {
    addedManaged.Dispose();         
    base.DisposeManaged();
  }
  protected override void DisposeUnmanaged()
  {
    CloseHandle(addedNative);
    base.DisposeUnmanaged();
  }
}
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: Правила использования финалайзеров [blog]
От: TK Лес кывт.рф
Дата: 23.01.06 09:46
Оценка:
Hello, "Sinclair"
>
> TK>Между тем, они уже есть... Например, флаг disposed из BaseResource в
> MyResourceWrapper уже не доступен. А объявленное поле никак не
> используется... DisposeUnmanaged(false) в финализаторе тоже никак не
> понятен.
> Виноват, писал с листа. Вот еще один подход:
>

Все равно, решение сложное

Например, нет атомарности освобождения ресурсов. Например, кто-то кидает
исключение в DisposeManaged. В результате до освобождения unmanaged ресурсов
дело уже не дойдет. В случае использования стандартного шаблона,
освобождение своих ресурсов так или иначе гарантировать...
Posted via RSDN NNTP Server 2.0
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[7]: Правила использования финалайзеров [blog]
От: Merle Австрия http://rsdn.ru
Дата: 23.01.06 10:02
Оценка:
Здравствуйте, TK, Вы писали:

TK>Все равно, решение сложное

Хм... Оно если и не более простое, то все-таки более ООП-истое и более очевидное.

TK>В случае использования стандартного шаблона,

TK>освобождение своих ресурсов так или иначе гарантировать...
... все равно не получится. В любом случае, если у тебя в Dispose() вылетит exception, то дальше ничего не пройдет.
И если уж этого опасаться, то в Тохином варианте всегда можно сделать
    if (!disposed)
    {
            try
            {
                DisposeManaged();
            }
            ...
            finally
            {
                DisposeUnmanaged();
            }
      _disposed = true;
      GC.SuppressFinalize(this);
    }
... << RSDN@Home 1.2.0 alpha rev. 0>>
Мы уже победили, просто это еще не так заметно...
Re[8]: Правила использования финалайзеров [blog]
От: TK Лес кывт.рф
Дата: 23.01.06 10:45
Оценка:
Hello, "Merle"

> TK>Все равно, решение сложное

> Хм... Оно если и не более простое, то все-таки более ООП-истое и более
> очевидное.
>

Оно настолько очевидное, что более/менее правильный вариант удалось только с
третьего раза написать?
Кстати, если я хочу вызвать GC.ReRegisterForFinalize то, где это лучше
сделать, в DisposeUnmanaged? тоже, очевидное решение... Так же, действия в
финализаторе могут быть не обязательно связаны с unmanaged ресурсами...
Вобщем, название метода DisposeUnmanaged удачным назвать нельзя...

Да и потом, как узнать в DisposeUnmanaged что именно происходит с объектом —
был вызван Dispose или запустился финализатор. Флаг завести?
Posted via RSDN NNTP Server 2.0
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[9]: Правила использования финалайзеров [blog]
От: mihailik Украина  
Дата: 23.01.06 11:16
Оценка:
TK>Оно настолько очевидное, что более/менее правильный вариант удалось только с
TK>третьего раза написать?
TK>Кстати, если я хочу вызвать GC.ReRegisterForFinalize то, где это лучше
TK>сделать, в DisposeUnmanaged? тоже, очевидное решение... Так же, действия в
TK>финализаторе могут быть не обязательно связаны с unmanaged ресурсами...
TK>Вобщем, название метода DisposeUnmanaged удачным назвать нельзя...

TK>Да и потом, как узнать в DisposeUnmanaged что именно происходит с объектом —

TK>был вызван Dispose или запустился финализатор. Флаг завести?

Вот именно эти проблемы-выкрутасы и привели к посту, с которого всё началось.

ИЗБЕГАЙТЕ ФИНАЛИЗАТОРОВ!
-заторов.. -аторов...


Finalize — это не пила, к которой есть стандартная техника безопасности. Скорее это ядерный реактор, который для каждого отдельного случая отдельно настраивается и продумывается.
Re[9]: Правила использования финалайзеров [blog]
От: Merle Австрия http://rsdn.ru
Дата: 23.01.06 11:56
Оценка:
Здравствуйте, TK, Вы писали:

TK>Оно настолько очевидное, что более/менее правильный вариант удалось только с

TK>третьего раза написать?
Дело не в очевидности написания, а в очевидности использования.

TK>Кстати, если я хочу вызвать GC.ReRegisterForFinalize то, где это лучше

TK>сделать, в DisposeUnmanaged?
Не, отдельную кнопку приделать.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Мы уже победили, просто это еще не так заметно...
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.