ОНОВЛЕННЯ : я використав це питання як основу для статті, яку можна знайти тут ; див. для додаткового обговорення цього питання. Дякую за гарне запитання!
Хоча відповідь Шабсе, безумовно, правильна і відповідає на запитання, яке було задано, у вашому питанні є важливий варіант, якого ви не задавали:
Що станеться, якщо 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заповнюється, якщо і лише тоді, коли його потрібно буде розмістити.
Опис прийомів цього виходить за межі цієї відповіді. Якщо у вас є ця вимога, зверніться до експерта.