ОНОВЛЕННЯ : я використав це питання як основу для статті, яку можна знайти тут ; див. для додаткового обговорення цього питання. Дякую за гарне запитання!
Хоча відповідь Шабсе, безумовно, правильна і відповідає на запитання, яке було задано, у вашому питанні є важливий варіант, якого ви не задавали:
Що станеться, якщо font4 = new Font()
викиди після того, як некерований ресурс був призначений конструктором, але до того, як ctor повернеться і заповнить font4
посилання?
Дозвольте зробити це трохи більш зрозумілим. Припустимо, у нас є:
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
Зараз у нас є
using(Foo foo = new Foo())
Whatever(foo);
Це те саме, що
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
ГАРАЗД. Припустимо, Whatever
кидки. Потім finally
блок запускається і ресурс розміщується. Нема проблем.
Припустимо, Blah1()
кидки. Тоді викид відбувається до виділення ресурсу. Об'єкт виділено, але ctor ніколи не повертається, тому foo
ніколи не заповнюється. Ми ніколи не входили try
так, ми ніколи не вводимо finally
жодне. Посилання на об'єкт осиротіла. Врешті-решт, GC виявить це і поставить його до черги фіналізатора. handle
досі дорівнює нулю, тому фіналізатор нічого не робить. Зауважте, що фіналізатор повинен бути надійним перед об'єктом, який доопрацьовується, конструктор якого так і не завершився . Ви повинні написати фіналізатори, які є сильними. Це ще одна причина, чому ви повинні залишити писати фіналізатори експертам, а не намагатися зробити це самостійно.
Припустимо, Blah3()
кидки. Кидок відбувається після виділення ресурсу. Але знову ж таки, foo
ніколи не заповнюється, ми ніколи не входимо вfinally
в об'єкт, і об'єкт очищається потоком фіналізатора. Цього разу ручка не дорівнює нулю, і фіналізатор очищає її. Знову фіналізатор працює на об'єкті, конструктор якого ніколи не вдавався, але фіналізатор все одно працює. Очевидно, що це мусить, бо цього разу йому довелося попрацювати.
Тепер припустимо, Blah2()
кидки. Кидок відбувається після виділення ресурсу, але до того, як handle
буде заповнено! Знову фіналізатор запуститься, але зараз handle
ще нуль, і ми просочимо ручку!
Вам потрібно написати надзвичайно розумний код, щоб не допустити цього витоку. Тепер, що стосується вашого Font
ресурсу, хто до біса піклується? Ми просочуємо ручку шрифту, велика справа. Але якщо ви абсолютно позитивно вимагаєте, щоб кожен некерований ресурс був очищений, незалежно від часу винятків тоді у вас дуже складна проблема.
CLR має вирішити цю проблему із замками. Оскільки C # 4, блоки, які використовують lock
оператор, реалізовані так:
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Enter
було дуже ретельно написано так, що незалежно від того, які винятки викидаються , lockEntered
встановлено значення "true", якщо і лише тоді, коли блокування було зроблено насправді. Якщо у вас є подібні вимоги, тоді вам потрібно написати:
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
і пишіть AllocateResource
розумно Monitor.Enter
так, щоб незалежно від того, що відбувається всередині AllocateResource
, handle
заповнюється, якщо і лише тоді, коли його потрібно буде розмістити.
Опис прийомів цього виходить за межі цієї відповіді. Якщо у вас є ця вимога, зверніться до експерта.